76 Commits

Author SHA1 Message Date
278e851414 Rename String.toString to String.get()
Last changed it only by the Range and forgotten about the string itself.
2017-06-08 07:59:16 +02:00
6f549df243 Update README description 2017-06-07 08:04:50 +02:00
4633bcc680 Set: Fix comparing with removed elements 2017-06-07 07:57:22 +02:00
dc39efd316 Add some unit tests for InternetAddress 2017-06-03 15:18:53 +02:00
260937e4fb Put socket overlapped I/O docs into a D_Ddoc block 2017-06-03 13:20:32 +02:00
e17fff2881 Update 2.074 compiler 2017-06-02 22:01:13 +02:00
bc32511254 Fix template parameters for Set 2017-06-01 22:36:38 +02:00
f30972f948 Add basic constructors and opAssign to Set 2017-06-01 06:26:06 +02:00
ea33ca62c8 Implement lookups in the Set 2017-05-31 10:29:07 +02:00
0f365758e1 Add optional fieldnames for Pair 2017-05-30 20:20:20 +02:00
2815b53a88 Implement Set Range 2017-05-30 15:52:18 +02:00
6c0588164a Rename String.toString back to get()
Since it is expected that the return type of toString() is
immutable(char)[] and not char[] or const(char)[].
2017-05-29 11:41:49 +02:00
8ee1d647ce Close issue 212
Introduce Range and constRange aliases for containers.
2017-05-29 11:26:39 +02:00
25791775e6 Add information about the Set to README 2017-05-29 10:58:37 +02:00
f013e2f1f4 Implement a Set container first 2017-05-29 10:50:01 +02:00
ac3935d71b Merge branch 'master' into horton-table 2017-05-28 10:15:02 +02:00
b1c217e272 Fix kqueue to work with SocketType 2017-05-25 22:21:45 +02:00
d007aaa310 Rename socket_t to SocketType 2017-05-25 21:59:40 +02:00
8aaf9e14be Add HashTable struct 2017-05-23 22:17:35 +02:00
ae3e6b46f6 Import std.algorithm.comparison for network.socket on Windows 2017-05-21 10:25:54 +02:00
8687df1fbb Define AddressFamily in network.socket 2017-05-21 10:20:57 +02:00
ba0aff6737 Add tanya.typecons.Pair 2017-05-19 21:15:56 +02:00
a648e2120a Fix parameter count for docs in container.string 2017-05-19 20:01:04 +02:00
bc61809050 Implement DList.insertBack 2017-05-16 13:16:18 +02:00
8c42cbfd63 Rename Vector to Array
For consistency with Phobos.
2017-05-16 12:12:57 +02:00
58664570f9 Add new branch, add DList to package description 2017-05-15 20:09:32 +02:00
decb82f437 Remove crypto.mac for now
It wasn't released yet and needs some work.
2017-05-15 19:57:36 +02:00
357c7e279d Add doubly-linked list
DList is an adjusted copy of SList. Missing:
* insertBack
* insertAfter
* removeBack
2017-05-15 19:50:20 +02:00
32e19c8b58 Rename String.get into toString. Add String.toStringz 2017-05-14 11:56:57 +02:00
f5c6c5b483 Add Payload template for memory.types 2017-05-13 08:43:49 +02:00
ba2d086fb8 Add memory.types.Scoped 2017-05-13 08:27:51 +02:00
7a0241b484 Fix unittest text for strings 2017-05-12 22:42:43 +02:00
36dad80e18 Use char ranges to avoid compilation errors on elder compilers 2017-05-12 21:46:48 +02:00
29d883150e Fix unittests on 2.072 and 2.071 2017-05-12 21:10:22 +02:00
e2bed0cfcb Replace tabs with spaces in mmappool and buffer 2017-05-12 21:02:24 +02:00
38afeac071 Insert String.insertFront and String.insertBack 2017-05-12 20:55:42 +02:00
001c7c3e33 Replace immutable with const in Vector 2017-05-12 20:35:16 +02:00
d4ab339feb Add String.remove 2017-05-12 20:23:16 +02:00
8477312769 Add editorconfig 2017-05-11 13:57:24 +02:00
67f90e137d Add codecov badge 2017-05-11 13:15:04 +02:00
f264fd5597 Generate unittest coverage information 2017-05-11 13:11:12 +02:00
9e75620f1b Fix appveyor branch badges 2017-05-11 07:05:13 +02:00
45825946c0 Appveyor (#10)
* Add appveyor.yml

* Try major VC version

* Switch to VC 2015

* Try new version

* Try enterprise

* Try another path

* Change VC template

* Set arch

* Set LINKCMD64

* Fix quotes

* Update LINKCMD64

* remove dir

* Update arch

* Fix syntax

* Set arch to x64

* Remove extra dub downloading

* Remove dub version

* Download dub for 2.071.2

* Use DVersion

* Fix nul in powershell

* Put quotes to commands

* Add badges
2017-05-11 06:26:59 +02:00
8afb552d59 mp.Integer: add two's complement constructor 2017-05-10 19:27:25 +02:00
e4091669f8 Add information about io branch 2017-05-10 13:18:58 +02:00
1cb9349226 math.mp.Integer.toVector return two's complement 2017-05-09 06:27:30 +02:00
06620dc5df math.mp.Integer: Return two's complement length 2017-05-08 21:09:52 +02:00
708d95db49 Remove utf8string branch 2017-05-06 11:55:20 +02:00
85d9361bfb Fix fill with char on older compilers 2017-05-05 07:03:16 +02:00
a6a6f496eb Implement string slice assignments 2017-05-04 23:17:50 +02:00
db12f03264 Merge branch 'master' into utf8string 2017-05-03 19:15:13 +02:00
231aedb8ad Add HMAC 2017-05-03 19:05:23 +02:00
c3b63ee40d Merge branch 'master' into utf8string 2017-05-02 10:59:00 +02:00
6f405c5e08 Make Vector's opSliceAssign accept only own ranges
Vector.opSliceAssign and Vector.opIndexAssign should accept only vector
ranges. For assigning other ranges, std.algorithm.mutation.copy and
std.algorithm.mutation.fill should be used.
2017-05-02 10:56:32 +02:00
16cf8478cf Add ByCodePoint 2017-05-01 20:17:37 +02:00
8915a0c7a7 Implement opCmp and opEquals for the String 2017-05-01 18:43:12 +02:00
e5c7edb72c Implement String opAssign 2017-05-01 12:58:37 +02:00
64e0d666ed Merge branch 'master' of github.com:caraus-ecms/tanya into utf8string 2017-05-01 09:59:29 +02:00
f2aac680c5 Fix container ctors and opAssign ref parameters
Container constructors and opAssign should accept any ref container and
not only const, otherwise the source container will be copied because
the constructor/opAssign without ref would be a better match.
2017-05-01 09:48:12 +02:00
d629525a4b Make String to be a char Slice alias 2017-04-21 14:03:20 +02:00
33d321f0d7 Merge branch 'master' into utf8string 2017-04-20 17:32:59 +02:00
3d64d59ba9 Merge branch 'master' of github.com:caraus-ecms/tanya 2017-04-20 17:32:29 +02:00
4635835a99 Rename Vector range to Slice 2017-04-20 17:32:16 +02:00
8725ec5f20 Make Integer representation little endian 2017-04-19 13:49:44 +02:00
9a4c8cea06 Merge branch 'master' into utf8string 2017-04-16 20:52:40 +02:00
eb360bda38 Add unittest to check RefCounted calles struct destructors 2017-04-16 20:52:24 +02:00
4b1cd2cbfd Merge branch 'master' into utf8string 2017-04-16 20:15:11 +02:00
cd944a61b7 Merge remote-tracking branch 'origin/master' into utf8string 2017-04-13 16:03:00 +02:00
47ef787353 Add missing constructors to the String 2017-04-10 08:10:08 +02:00
6436ad49df Add ByteRange to the String 2017-04-08 17:44:08 +02:00
e1964e47a5 Merge branch 'master' into utf8string 2017-04-08 08:44:21 +02:00
43319e4e3a Initialization from a UTF-16 string 2017-02-27 11:27:24 +01:00
33dbf042c2 Add dchar constructor 2017-02-26 22:40:27 +01:00
885fca9b5e Add String.reserve and shrink 2017-02-20 12:01:15 +01:00
074d027629 Merge branch 'master' into utf8string 2017-02-20 08:02:01 +01:00
f4b90d8b51 Add string skeleton 2017-02-10 19:22:46 +01:00
27 changed files with 8056 additions and 3895 deletions

9
.editorconfig Normal file
View 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

View File

@ -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)

View File

@ -1,6 +1,8 @@
# Tanya # Tanya
[![Build Status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya) [![Build status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
[![Build status](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/master?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
[![codecov](https://codecov.io/gh/caraus-ecms/tanya/branch/master/graph/badge.svg)](https://codecov.io/gh/caraus-ecms/tanya)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya) [![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya) [![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE) [![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](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 | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
|--------------|:-----------------------------------------------------------------------------------------------------------------------:| | TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
| UTF-8 string | [![utf8string](https://travis-ci.org/caraus-ecms/tanya.svg?branch=utf8string)](https://travis-ci.org/caraus-ecms/tanya) | | File IO | io | [![io](https://travis-ci.org/caraus-ecms/tanya.svg?branch=io)](https://travis-ci.org/caraus-ecms/tanya) [![io](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/io?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
| BitVector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=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
View 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%

View File

@ -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": [

View File

@ -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);
} }
/** /**

View File

@ -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)
{ {

View File

@ -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);

View File

@ -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;
* } * }
* *

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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);
}
}

View 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));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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[]));
} }
} }

View File

@ -18,7 +18,19 @@ import std.range;
import std.traits; import std.traits;
import tanya.memory; import tanya.memory;
package(tanya) final class RefCountedStore(T) private template Payload(T)
{
static if (is(T == class) || is(T == interface) || isArray!T)
{
alias Payload = T;
}
else
{
alias Payload = T*;
}
}
final class RefCountedStore(T)
{ {
T payload; T payload;
size_t counter = 1; size_t counter = 1;
@ -34,7 +46,7 @@ package(tanya) final class RefCountedStore(T)
mixin("return " ~ op ~ "counter;"); mixin("return " ~ op ~ "counter;");
} }
int opCmp(size_t counter) int opCmp(const size_t counter)
{ {
if (this.counter > counter) if (this.counter > counter)
{ {
@ -50,7 +62,7 @@ package(tanya) final class RefCountedStore(T)
} }
} }
int opEquals(size_t counter) int opEquals(const size_t counter)
{ {
return this.counter == counter; return this.counter == counter;
} }
@ -81,15 +93,7 @@ private void unifiedDeleter(T)(RefCountedStore!T storage,
*/ */
struct RefCounted(T) struct RefCounted(T)
{ {
static if (is(T == class) || is(T == interface) || isArray!T) private alias Storage = RefCountedStore!(Payload!T);
{
private alias Payload = T;
}
else
{
private alias Payload = T*;
}
private alias Storage = RefCountedStore!Payload;
private Storage storage; private Storage storage;
private void function(Storage storage, private void function(Storage storage,
@ -112,13 +116,18 @@ struct RefCounted(T)
* *
* Precondition: $(D_INLINECODE allocator !is null) * Precondition: $(D_INLINECODE allocator !is null)
*/ */
this()(auto ref Payload value, this()(auto ref Payload!T value,
shared Allocator allocator = defaultAllocator) shared Allocator allocator = defaultAllocator)
{ {
this(allocator); this(allocator);
this.storage = allocator.make!Storage(); this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!Payload; this.deleter = &separateDeleter!(Payload!T);
move(value, this.storage.payload); move(value, this.storage.payload);
static if (__traits(isRef, value))
{
value = null;
}
} }
/// Ditto. /// Ditto.
@ -139,7 +148,7 @@ struct RefCounted(T)
{ {
if (count != 0) if (count != 0)
{ {
++storage; ++this.storage;
} }
} }
@ -152,7 +161,7 @@ struct RefCounted(T)
{ {
if (this.storage !is null && !(this.storage.counter && --this.storage)) if (this.storage !is null && !(this.storage.counter && --this.storage))
{ {
deleter(storage, allocator); deleter(this.storage, allocator);
} }
} }
@ -163,34 +172,34 @@ struct RefCounted(T)
* If it is the last reference of the previously owned object, * If it is the last reference of the previously owned object,
* it will be destroyed. * it will be destroyed.
* *
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null). * To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
* *
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new * be used. If you need a different allocator, create a new
* $(D_PSYMBOL RefCounted) and assign it. * $(D_PSYMBOL RefCounted) and assign it.
* *
* Params: * Params:
* rhs = $(D_KEYWORD this). * rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/ */
ref typeof(this) opAssign()(auto ref Payload rhs) ref typeof(this) opAssign()(auto ref Payload!T rhs)
{ {
if (this.storage is null) if (this.storage is null)
{ {
this.storage = allocator.make!Storage(); this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!Payload; this.deleter = &separateDeleter!(Payload!T);
} }
else if (this.storage > 1) else if (this.storage > 1)
{ {
--this.storage; --this.storage;
this.storage = allocator.make!Storage(); this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!Payload; this.deleter = &separateDeleter!(Payload!T);
} }
else else
{ {
// Created with refCounted. Always destroyed togethter with the pointer.
assert(this.storage.counter != 0);
finalize(this.storage.payload); finalize(this.storage.payload);
this.storage.payload = Payload.init; this.storage.payload = Payload!T.init;
} }
move(rhs, this.storage.payload); move(rhs, this.storage.payload);
return this; return this;
@ -206,15 +215,13 @@ struct RefCounted(T)
else if (this.storage > 1) else if (this.storage > 1)
{ {
--this.storage; --this.storage;
this.storage = null;
} }
else else
{ {
// Created with refCounted. Always destroyed togethter with the pointer. deleter(this.storage, allocator);
assert(this.storage.counter != 0);
finalize(this.storage.payload);
this.storage.payload = Payload.init;
} }
this.storage = null;
return this; return this;
} }
@ -229,8 +236,10 @@ struct RefCounted(T)
/** /**
* Returns: Reference to the owned object. * Returns: Reference to the owned object.
*
* Precondition: $(D_INLINECODE cound > 0).
*/ */
inout(Payload) get() inout pure nothrow @safe @nogc Payload!T get() pure nothrow @safe @nogc
in in
{ {
assert(count > 0, "Attempted to access an uninitialized reference."); assert(count > 0, "Attempted to access an uninitialized reference.");
@ -240,7 +249,7 @@ struct RefCounted(T)
return storage.payload; return storage.payload;
} }
static if (isPointer!Payload) version (D_Ddoc)
{ {
/** /**
* Params: * Params:
@ -251,6 +260,11 @@ struct RefCounted(T)
* *
* Returns: Reference to the pointed value. * Returns: Reference to the pointed value.
*/ */
ref T opUnary(string op)()
if (op == "*");
}
else static if (isPointer!(Payload!T))
{
ref T opUnary(string op)() ref T opUnary(string op)()
if (op == "*") if (op == "*")
{ {
@ -352,6 +366,44 @@ private unittest
assert(rc.count == 1); assert(rc.count == 1);
} }
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int rc)
{
assert(rc.count == 2);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
assert(rc.count == 1);
func(rc);
assert(rc.count == 1);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(*rc == 5);
void func(RefCounted!int rc)
{
assert(rc.count == 2);
rc = defaultAllocator.refCounted!int(4);
assert(*rc == 4);
assert(rc.count == 1);
}
func(rc);
assert(*rc == 5);
}
private unittest private unittest
{ {
static assert(is(typeof(RefCounted!int.storage.payload) == int*)); static assert(is(typeof(RefCounted!int.storage.payload) == int*));
@ -414,13 +466,13 @@ body
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))(); auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
rc.storage.payload = emplace!T(ptr, args); rc.storage.payload = emplace!T(ptr, args);
} }
rc.deleter = &unifiedDeleter!(RefCounted!T.Payload); rc.deleter = &unifiedDeleter!(Payload!T);
return rc; return rc;
} }
/** /**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a * Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL RefCounted) using. * $(D_PSYMBOL RefCounted).
* *
* Params: * Params:
* T = Array type. * T = Array type.
@ -432,7 +484,8 @@ body
* Precondition: $(D_INLINECODE allocator !is null * Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof) * && size <= size_t.max / ElementType!T.sizeof)
*/ */
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size) RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T) if (isArray!T)
in in
{ {
@ -441,8 +494,7 @@ in
} }
body body
{ {
auto payload = cast(T) allocator.allocate(ElementType!T.sizeof * size); auto payload = allocator.resize!(ElementType!T)(null, size);
initializeAll(payload);
return RefCounted!T(payload, allocator); return RefCounted!T(payload, allocator);
} }
@ -495,3 +547,266 @@ private @nogc unittest
auto rc = defaultAllocator.refCounted!(int[])(5); auto rc = defaultAllocator.refCounted!(int[])(5);
assert(rc.length == 5); assert(rc.length == 5);
} }
private @nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto rc = defaultAllocator.refCounted!F();
}
assert(destroyed);
}
/**
* $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope.
*
* Params:
* T = Value type.
*/
struct Scoped(T)
{
private Payload!T payload;
invariant
{
assert(payload is null || allocator_ !is null);
}
/**
* Takes ownership over $(D_PARAM value), setting the counter to 1.
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
*
* Params:
* value = Value whose ownership is taken over.
* allocator = Allocator used to destroy the $(D_PARAM value) and to
* allocate/deallocate internal storage.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
this()(auto ref Payload!T value,
shared Allocator allocator = defaultAllocator)
{
this(allocator);
move(value, this.payload);
static if (__traits(isRef, value))
{
value = null;
}
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
/**
* $(D_PSYMBOL Scoped) is noncopyable.
*/
@disable this(this);
/**
* Destroys the owned object.
*/
~this()
{
if (this.payload !is null)
{
allocator.dispose(this.payload);
}
}
/**
* Initialized this $(D_PARAM Scoped) and takes ownership over
* $(D_PARAM rhs).
*
* To reset $(D_PSYMBOL Scoped) assign $(D_KEYWORD null).
*
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new
* $(D_PSYMBOL Scoped) and assign it.
*
* Params:
* rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign()(auto ref Payload!T rhs)
{
allocator.dispose(this.payload);
move(rhs, this.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(null))
{
allocator.dispose(this.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
swap(this.payload, rhs.payload);
return this;
}
/**
* Returns: Reference to the owned object.
*/
Payload!T get() pure nothrow @safe @nogc
{
return payload;
}
version (D_Ddoc)
{
/**
* Params:
* op = Operation.
*
* Dereferences the pointer. It is defined only for pointers, not for
* reference types like classes, that can be accessed directly.
*
* Returns: Reference to the pointed value.
*/
ref T opUnary(string op)()
if (op == "*");
}
else static if (isPointer!(Payload!T))
{
ref T opUnary(string op)()
if (op == "*")
{
return *payload;
}
}
mixin DefaultAllocator;
alias get this;
}
///
@nogc unittest
{
auto p = defaultAllocator.make!int(5);
auto s = Scoped!int(p, defaultAllocator);
assert(p is null);
assert(*s == 5);
}
///
@nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator);
}
assert(destroyed);
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL Scoped) using $(D_PARAM args) as the parameter list for
* the constructor of $(D_PARAM T).
*
* Params:
* T = Type of the constructed object.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL Scoped!T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
{
auto payload = allocator.make!(T, shared Allocator, A)(args);
return Scoped!T(payload, allocator);
}
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL Scoped).
*
* Params:
* T = Array type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL Scoped!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
*/
Scoped!T scoped(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
body
{
auto payload = allocator.resize!(ElementType!T)(null, size);
return Scoped!T(payload, allocator);
}
private unittest
{
static assert(is(typeof(defaultAllocator.scoped!B(5))));
static assert(is(typeof(defaultAllocator.scoped!(int[])(5))));
}
private unittest
{
auto s = defaultAllocator.scoped!int(5);
assert(*s == 5);
s = null;
assert(s is null);
}
private unittest
{
auto s = defaultAllocator.scoped!int(5);
assert(*s == 5);
s = defaultAllocator.scoped!int(4);
assert(*s == 4);
}

View File

@ -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
{
/// 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)
{ {
defaultAllocator.dispose(socket); 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");
} }
@ -771,7 +961,7 @@ abstract class Socket
SocketOption option, SocketOption option,
out size_t result) const @trusted @nogc out size_t result) const @trusted @nogc
{ {
return getOption(level, option, (&result)[0..1]); return getOption(level, option, (&result)[0 .. 1]);
} }
/// Ditto. /// Ditto.
@ -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.
@ -792,7 +982,7 @@ abstract class Socket
version (Posix) version (Posix)
{ {
timeval tv; timeval tv;
auto ret = getOption(level, option, (&tv)[0..1]); auto ret = getOption(level, option, (&tv)[0 .. 1]);
result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec); result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec);
} }
else version (Windows) else version (Windows)
@ -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");
} }
@ -836,14 +1026,14 @@ abstract class Socket
void setOption(SocketOptionLevel level, SocketOption option, size_t value) void setOption(SocketOptionLevel level, SocketOption option, size_t value)
const @trusted @nogc const @trusted @nogc
{ {
setOption(level, option, (&value)[0..1]); setOption(level, option, (&value)[0 .. 1]);
} }
/// Ditto. /// Ditto.
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.
@ -854,7 +1044,7 @@ abstract class Socket
{ {
timeval tv; timeval tv;
value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec); value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
setOption(level, option, (&tv)[0..1]); setOption(level, option, (&tv)[0 .. 1]);
} }
else version (Windows) else version (Windows)
{ {
@ -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,11 +1475,11 @@ 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);
node[0.. $ - 1] = host; node[0 .. $ - 1] = host;
node[$ - 1] = '\0'; node[$ - 1] = '\0';
scope (exit) scope (exit)
{ {
@ -1304,18 +1491,19 @@ 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;
} }
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res); auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
@ -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
View 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")));
}