108 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
65c3ca14ec Integer storage optimization 2017-04-30 16:07:44 +02:00
4fa47153ba Make Integer representation little endian 2017-04-25 19:50:06 +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
628153e2e8 Make RefCounted work with dynamic arrays 2017-04-16 20:14:04 +02:00
7aa9ac9f4a Add internal finalize method for finalizing an object without deallocating 2017-04-16 20:13:20 +02:00
cd944a61b7 Merge remote-tracking branch 'origin/master' into utf8string 2017-04-13 16:03:00 +02:00
8156d0fe3a Add support for dmd 2.074.0, remove 2.070.2 2017-04-13 16:02:18 +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
6e2ce5d686 Remove opApply from containers
opApply requires additional overloads for the const containers (with a
const delegate). If using a templated opApply foreach cannot infer the
types for the variables. foreach with one argument still works
(opIndex() is used), for more complex cases slicing should be used.
2017-04-07 16:00:50 +02:00
ba6bf554fb Make SList range public 2017-04-07 15:17:14 +02:00
b1d2b9bd9e Fix Vector.insertAfter/Before an empty range 2017-04-04 15:11:14 +02:00
9b953198fa Fix network.inet release build 2017-04-04 08:36:42 +02:00
bc2a6d2703 Swap toHostOrder template parameters 2017-04-03 15:32:15 +02:00
b458250ad7 Make NetworkOrder work with 8-byte integers 2017-04-02 20:55:22 +02:00
b08d5e5d83 Add tanya.network.inet.toHostOrder
The function reverts NetworkOrder.
2017-04-02 11:16:08 +02:00
445b872e91 Add tanya.network.inet.NetworkOrder
NetworkOrder converts an integral type into a bidirectional range with
big-endian byte order.
2017-04-02 09:29:54 +02:00
5e16fe98d6 Add tanya.network package file 2017-04-01 09:53:59 +02:00
647cfe03c2 Update latest supported compiler 2017-03-29 17:23:10 +02:00
4cd6126d6b Fix SList documentation for insertFront and insertBefore 2017-03-29 17:22:25 +02:00
b870179a35 Move bitvector to another branch till it is finished 2017-03-29 11:17:03 +02:00
aabb4fb534 Add SList.opAssign 2017-03-29 10:35:45 +02:00
4d8b95812e Implement opAssign for the Vector 2017-03-28 20:42:42 +02:00
e921413249 Merge branch 'master' of github.com:caraus-ecms/tanya 2017-03-24 20:54:47 +01:00
49cae88645 Add insertBefore and remove to SList 2017-03-24 20:54:28 +01:00
402fdfae89 math.mp: Fix initialization issues after resizing 2017-03-23 15:36:17 +01:00
7892c1a930 Remove Init template parameter from memory.resize() 2017-03-22 08:51:00 +01:00
b90517580e Merge math.mp.Integer changes from the crypto branch 2017-03-21 19:25:12 +01:00
85380ac3fc Remove makeArray import 2017-03-19 06:54:59 +01:00
b90c56395c Remove resizeArray alias 2017-03-19 06:10:27 +01:00
d0ada39fa7 Add Mallocator as an alternative allocator 2017-03-18 08:07:01 +01:00
f4145abfd1 Add SList constructors 2017-03-09 06:07:23 +01:00
093d499729 Fix element order inserted from a range into list 2017-03-08 07:12:23 +01:00
f90a03501b Move BitVector from the crypto branch 2017-03-02 11:27:26 +01:00
c6a99b114e SList.insertFront for ranges 2017-03-01 19:23:54 +01: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
3c23aca6a6 Improve Vector module and reserve documentation 2017-02-20 12:03:49 +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
36 changed files with 13994 additions and 8286 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,14 +7,17 @@ os:
language: d language: d
d: d:
- dmd-2.073.0 - dmd-2.074.1
- dmd-2.073.2
- dmd-2.072.2 - dmd-2.072.2
- dmd-2.071.2 - dmd-2.071.2
- dmd-2.070.2
env: env:
matrix: matrix:
- 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,46 +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 2.073.0 | dmd |
* dmd 2.072.2 |:-------:|
* dmd 2.071.2 | 2.074.1 |
* dmd 2.070.2 | 2.073.2 |
| 2.072.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.
`container`s are being extended to support ranges. Also following modules are | Feature | Branch | Build status |
coming soon: |----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
* UTF-8 string. | 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) |
* Hash table. | 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) |
| 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) |
`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;
@ -29,153 +29,153 @@ import std.algorithm.comparison;
extern (C) nothrow @nogc extern (C) nothrow @nogc
{ {
int epoll_create1(int flags); int epoll_create1(int flags);
int epoll_ctl (int epfd, int op, int fd, epoll_event *event); int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout); int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout);
} }
final class EpollLoop : SelectorLoop final class EpollLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private Vector!epoll_event events; private Array!epoll_event events;
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
this() @nogc this() @nogc
{ {
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0) if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
{ {
throw defaultAllocator.make!BadLoopException("epoll initialization failed"); throw defaultAllocator.make!BadLoopException("epoll initialization failed");
} }
super(); super();
events = Vector!epoll_event(maxEvents, MmapPool.instance); events = Array!epoll_event(maxEvents, MmapPool.instance);
} }
/** /**
* Frees loop internals. * Frees loop internals.
*/ */
~this() @nogc ~this() @nogc
{ {
close(fd); close(fd);
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
protected override bool reify(SocketWatcher watcher, protected override bool reify(SocketWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) @nogc EventMask events) @nogc
{ {
int op = EPOLL_CTL_DEL; int op = EPOLL_CTL_DEL;
epoll_event ev; epoll_event ev;
if (events == oldEvents) if (events == oldEvents)
{ {
return true; return true;
} }
if (events && oldEvents) if (events && oldEvents)
{ {
op = EPOLL_CTL_MOD; op = EPOLL_CTL_MOD;
} }
else if (events && !oldEvents) else if (events && !oldEvents)
{ {
op = EPOLL_CTL_ADD; op = EPOLL_CTL_ADD;
} }
ev.data.fd = watcher.socket.handle; ev.data.fd = watcher.socket.handle;
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0) ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
| (events & Event.write ? EPOLLOUT : 0) | (events & Event.write ? EPOLLOUT : 0)
| EPOLLET; | EPOLLET;
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0; return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
protected override void poll() @nogc protected override void poll() @nogc
{ {
// Don't block // Don't block
immutable timeout = cast(immutable int) blockTime.total!"msecs"; immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout); auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
if (eventCount < 0) if (eventCount < 0)
{ {
if (errno != EINTR) if (errno != EINTR)
{ {
throw defaultAllocator.make!BadLoopException(); throw defaultAllocator.make!BadLoopException();
} }
return; return;
} }
for (auto i = 0; i < eventCount; ++i) for (auto i = 0; i < eventCount; ++i)
{ {
auto transport = cast(StreamTransport) connections[events[i].data.fd]; auto transport = cast(StreamTransport) connections[events[i].data.fd];
if (transport is null) if (transport is null)
{ {
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd]; auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
assert(connection !is null); assert(connection !is null);
acceptConnections(connection); acceptConnections(connection);
} }
else if (events[i].events & EPOLLERR) else if (events[i].events & EPOLLERR)
{ {
kill(transport); kill(transport);
continue; continue;
} }
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP)) else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
{ {
SocketException exception; SocketException exception;
try try
{ {
ptrdiff_t received; ptrdiff_t received;
do do
{ {
received = transport.socket.receive(transport.output[]); received = transport.socket.receive(transport.output[]);
transport.output += received; transport.output += received;
} }
while (received); while (received);
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
} }
if (transport.socket.disconnected) if (transport.socket.disconnected)
{ {
kill(transport, exception); kill(transport, exception);
continue; continue;
} }
else if (transport.output.length) else if (transport.output.length)
{ {
pendings.enqueue(transport); pendings.enqueue(transport);
} }
} }
if (events[i].events & EPOLLOUT) if (events[i].events & EPOLLOUT)
{ {
transport.writeReady = true; transport.writeReady = true;
if (transport.input.length) if (transport.input.length)
{ {
feed(transport); feed(transport);
} }
} }
} }
} }
/** /**
* Returns: The blocking time. * Returns: The blocking time.
*/ */
override protected @property inout(Duration) blockTime() override protected @property inout(Duration) blockTime()
inout @safe pure nothrow inout @safe pure nothrow
{ {
return min(super.blockTime, 1.dur!"seconds"); return min(super.blockTime, 1.dur!"seconds");
} }
} }

View File

@ -31,354 +31,354 @@ import core.sys.windows.winsock2;
*/ */
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{ {
private SocketException exception; private SocketException exception;
private ReadBuffer!ubyte output; private ReadBuffer!ubyte output;
private WriteBuffer!ubyte input; private WriteBuffer!ubyte input;
private Protocol protocol_; private Protocol protocol_;
private bool closing; private bool closing;
/** /**
* Creates new completion port transport. * Creates new completion port transport.
* *
* Params: * Params:
* socket = Socket. * socket = Socket.
* *
* Precondition: $(D_INLINECODE socket !is null) * Precondition: $(D_INLINECODE socket !is null)
*/ */
this(OverlappedConnectedSocket socket) @nogc this(OverlappedConnectedSocket socket) @nogc
{ {
super(socket); super(socket);
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance); input = WriteBuffer!ubyte(8192, MmapPool.instance);
active = true; active = true;
} }
/** /**
* Returns: Socket. * Returns: Socket.
* *
* Postcondition: $(D_INLINECODE socket !is null) * Postcondition: $(D_INLINECODE socket !is null)
*/ */
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
out (socket) out (socket)
{ {
assert(socket !is null); assert(socket !is null);
} }
body body
{ {
return cast(OverlappedConnectedSocket) socket_; return cast(OverlappedConnectedSocket) socket_;
} }
/** /**
* Returns $(D_PARAM true) if the transport is closing or closed. * Returns $(D_PARAM true) if the transport is closing or closed.
*/ */
bool isClosing() const pure nothrow @safe @nogc bool isClosing() const pure nothrow @safe @nogc
{ {
return closing; return closing;
} }
/** /**
* Close the transport. * Close the transport.
* *
* Buffered data will be flushed. No more data will be received. * Buffered data will be flushed. No more data will be received.
*/ */
void close() pure nothrow @safe @nogc void close() pure nothrow @safe @nogc
{ {
closing = true; closing = true;
} }
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* Params: * Params:
* data = Data to send. * data = Data to send.
*/ */
void write(ubyte[] data) @nogc void write(ubyte[] data) @nogc
{ {
input ~= data; input ~= data;
} }
/** /**
* Returns: Application protocol. * Returns: Application protocol.
*/ */
@property Protocol protocol() pure nothrow @safe @nogc @property Protocol protocol() pure nothrow @safe @nogc
{ {
return protocol_; return protocol_;
} }
/** /**
* Switches the protocol. * Switches the protocol.
* *
* The protocol is deallocated by the event loop, it should currently be * The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool). * allocated with $(D_PSYMBOL MmapPool).
* *
* Params: * Params:
* protocol = Application protocol. * protocol = Application protocol.
* *
* Precondition: $(D_INLINECODE protocol !is null) * Precondition: $(D_INLINECODE protocol !is null)
*/ */
@property void protocol(Protocol protocol) pure nothrow @safe @nogc @property void protocol(Protocol protocol) pure nothrow @safe @nogc
in in
{ {
assert(protocol !is null); assert(protocol !is null);
} }
body body
{ {
protocol_ = protocol; protocol_ = protocol;
} }
/** /**
* Invokes the watcher callback. * Invokes the watcher callback.
*/ */
override void invoke() @nogc override void invoke() @nogc
{ {
if (output.length) if (output.length)
{ {
immutable empty = input.length == 0; immutable empty = input.length == 0;
protocol.received(output[0 .. $]); protocol.received(output[0 .. $]);
output.clear(); output.clear();
if (empty) if (empty)
{ {
SocketState overlapped; SocketState overlapped;
try try
{ {
overlapped = MmapPool.instance.make!SocketState; overlapped = MmapPool.instance.make!SocketState;
socket.beginSend(input[], overlapped); socket.beginSend(input[], overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
MmapPool.instance.dispose(e); MmapPool.instance.dispose(e);
} }
} }
} }
else else
{ {
protocol.disconnected(exception); protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_); MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(exception); defaultAllocator.dispose(exception);
active = false; active = false;
} }
} }
} }
final class IOCPLoop : Loop final class IOCPLoop : Loop
{ {
protected HANDLE completionPort; protected HANDLE completionPort;
protected OVERLAPPED overlap; protected OVERLAPPED overlap;
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
this() @nogc this() @nogc
{ {
super(); super();
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (!completionPort) if (!completionPort)
{ {
throw make!BadLoopException(defaultAllocator, throw make!BadLoopException(defaultAllocator,
"Creating completion port failed"); "Creating completion port failed");
} }
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
override protected bool reify(SocketWatcher watcher, override protected bool reify(SocketWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) @nogc EventMask events) @nogc
{ {
SocketState overlapped; SocketState overlapped;
if (!(oldEvents & Event.accept) && (events & Event.accept)) if (!(oldEvents & Event.accept) && (events & Event.accept))
{ {
auto socket = cast(OverlappedStreamSocket) watcher.socket; auto socket = cast(OverlappedStreamSocket) watcher.socket;
assert(socket !is null); assert(socket !is null);
if (CreateIoCompletionPort(cast(HANDLE) socket.handle, if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
completionPort, completionPort,
cast(ULONG_PTR) (cast(void*) watcher), cast(ULONG_PTR) (cast(void*) watcher),
0) !is completionPort) 0) !is completionPort)
{ {
return false; return false;
} }
try try
{ {
overlapped = MmapPool.instance.make!SocketState; overlapped = MmapPool.instance.make!SocketState;
socket.beginAccept(overlapped); socket.beginAccept(overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(e); defaultAllocator.dispose(e);
return false; return false;
} }
} }
if (!(oldEvents & Event.read) && (events & Event.read) if (!(oldEvents & Event.read) && (events & Event.read)
|| !(oldEvents & Event.write) && (events & Event.write)) || !(oldEvents & Event.write) && (events & Event.write))
{ {
auto transport = cast(StreamTransport) watcher; auto transport = cast(StreamTransport) watcher;
assert(transport !is null); assert(transport !is null);
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle, if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
completionPort, completionPort,
cast(ULONG_PTR) (cast(void*) watcher), cast(ULONG_PTR) (cast(void*) watcher),
0) !is completionPort) 0) !is completionPort)
{ {
return false; return false;
} }
// Begin to read // Begin to read
if (!(oldEvents & Event.read) && (events & Event.read)) if (!(oldEvents & Event.read) && (events & Event.read))
{ {
try try
{ {
overlapped = MmapPool.instance.make!SocketState; overlapped = MmapPool.instance.make!SocketState;
transport.socket.beginReceive(transport.output[], overlapped); transport.socket.beginReceive(transport.output[], overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(e); defaultAllocator.dispose(e);
return false; return false;
} }
} }
} }
return true; return true;
} }
private void kill(StreamTransport transport, private void kill(StreamTransport transport,
SocketException exception = null) @nogc SocketException exception = null) @nogc
in in
{ {
assert(transport !is null); assert(transport !is null);
} }
body body
{ {
transport.socket.shutdown(); transport.socket.shutdown();
defaultAllocator.dispose(transport.socket); defaultAllocator.dispose(transport.socket);
transport.exception = exception; transport.exception = exception;
pendings.enqueue(transport); pendings.enqueue(transport);
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
override protected void poll() @nogc override protected void poll() @nogc
{ {
DWORD lpNumberOfBytes; DWORD lpNumberOfBytes;
ULONG_PTR key; ULONG_PTR key;
LPOVERLAPPED overlap; LPOVERLAPPED overlap;
immutable timeout = cast(immutable int) blockTime.total!"msecs"; immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto result = GetQueuedCompletionStatus(completionPort, auto result = GetQueuedCompletionStatus(completionPort,
&lpNumberOfBytes, &lpNumberOfBytes,
&key, &key,
&overlap, &overlap,
timeout); timeout);
if (result == FALSE && overlap == NULL) if (result == FALSE && overlap == NULL)
{ {
return; // Timeout return; // Timeout
} }
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8)); auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
assert(overlapped !is null); assert(overlapped !is null);
scope (failure) scope (failure)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
} }
switch (overlapped.event) switch (overlapped.event)
{ {
case OverlappedSocketEvent.accept: case OverlappedSocketEvent.accept:
auto connection = cast(ConnectionWatcher) (cast(void*) key); auto connection = cast(ConnectionWatcher) (cast(void*) key);
assert(connection !is null); assert(connection !is null);
auto listener = cast(OverlappedStreamSocket) connection.socket; auto listener = cast(OverlappedStreamSocket) connection.socket;
assert(listener !is null); assert(listener !is null);
auto socket = listener.endAccept(overlapped); auto socket = listener.endAccept(overlapped);
auto transport = MmapPool.instance.make!StreamTransport(socket); auto transport = MmapPool.instance.make!StreamTransport(socket);
connection.incoming.enqueue(transport); connection.incoming.enqueue(transport);
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write)); reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
pendings.enqueue(connection); pendings.enqueue(connection);
listener.beginAccept(overlapped); listener.beginAccept(overlapped);
break; break;
case OverlappedSocketEvent.read: case OverlappedSocketEvent.read:
auto transport = cast(StreamTransport) (cast(void*) key); auto transport = cast(StreamTransport) (cast(void*) key);
assert(transport !is null); assert(transport !is null);
if (!transport.active) if (!transport.active)
{ {
MmapPool.instance.dispose(transport); MmapPool.instance.dispose(transport);
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
return; return;
} }
int received; int received;
SocketException exception; SocketException exception;
try try
{ {
received = transport.socket.endReceive(overlapped); received = transport.socket.endReceive(overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
} }
if (transport.socket.disconnected) if (transport.socket.disconnected)
{ {
// We want to get one last notification to destroy the watcher. // We want to get one last notification to destroy the watcher.
transport.socket.beginReceive(transport.output[], overlapped); transport.socket.beginReceive(transport.output[], overlapped);
kill(transport, exception); kill(transport, exception);
} }
else if (received > 0) else if (received > 0)
{ {
immutable full = transport.output.free == received; immutable full = transport.output.free == received;
transport.output += received; transport.output += received;
// Receive was interrupted because the buffer is full. We have to continue. // Receive was interrupted because the buffer is full. We have to continue.
if (full) if (full)
{ {
transport.socket.beginReceive(transport.output[], overlapped); transport.socket.beginReceive(transport.output[], overlapped);
} }
pendings.enqueue(transport); pendings.enqueue(transport);
} }
break; break;
case OverlappedSocketEvent.write: case OverlappedSocketEvent.write:
auto transport = cast(StreamTransport) (cast(void*) key); auto transport = cast(StreamTransport) (cast(void*) key);
assert(transport !is null); assert(transport !is null);
transport.input += transport.socket.endSend(overlapped); transport.input += transport.socket.endSend(overlapped);
if (transport.input.length > 0) if (transport.input.length > 0)
{ {
transport.socket.beginSend(transport.input[], overlapped); transport.socket.beginSend(transport.input[], overlapped);
} }
else else
{ {
transport.socket.beginReceive(transport.output[], overlapped); transport.socket.beginReceive(transport.output[], overlapped);
if (transport.isClosing()) if (transport.isClosing())
{ {
kill(transport); kill(transport);
} }
} }
break; break;
default: default:
assert(false, "Unknown event"); assert(false, "Unknown event");
} }
} }
} }

View File

@ -12,31 +12,31 @@ module tanya.async.event.kqueue;
version (OSX) version (OSX)
{ {
version = MacBSD; version = MacBSD;
} }
else version (iOS) else version (iOS)
{ {
version = MacBSD; version = MacBSD;
} }
else version (TVOS) else version (TVOS)
{ {
version = MacBSD; version = MacBSD;
} }
else version (WatchOS) else version (WatchOS)
{ {
version = MacBSD; version = MacBSD;
} }
else version (FreeBSD) else version (FreeBSD)
{ {
version = MacBSD; version = MacBSD;
} }
else version (OpenBSD) else version (OpenBSD)
{ {
version = MacBSD; version = MacBSD;
} }
else version (DragonFlyBSD) else version (DragonFlyBSD)
{ {
version = MacBSD; version = MacBSD;
} }
version (MacBSD): version (MacBSD):
@ -50,62 +50,62 @@ import tanya.async.event.selector;
import tanya.async.loop; import tanya.async.loop;
import tanya.async.transport; import tanya.async.transport;
import tanya.async.watcher; import tanya.async.watcher;
import tanya.container.vector; import tanya.container.array;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool; import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
{ {
*kevp = kevent_t(args); *kevp = kevent_t(args);
} }
enum : short enum : short
{ {
EVFILT_READ = -1, EVFILT_READ = -1,
EVFILT_WRITE = -2, EVFILT_WRITE = -2,
EVFILT_AIO = -3, /* attached to aio requests */ EVFILT_AIO = -3, /* attached to aio requests */
EVFILT_VNODE = -4, /* attached to vnodes */ EVFILT_VNODE = -4, /* attached to vnodes */
EVFILT_PROC = -5, /* attached to struct proc */ EVFILT_PROC = -5, /* attached to struct proc */
EVFILT_SIGNAL = -6, /* attached to struct proc */ EVFILT_SIGNAL = -6, /* attached to struct proc */
EVFILT_TIMER = -7, /* timers */ EVFILT_TIMER = -7, /* timers */
EVFILT_MACHPORT = -8, /* Mach portsets */ EVFILT_MACHPORT = -8, /* Mach portsets */
EVFILT_FS = -9, /* filesystem events */ EVFILT_FS = -9, /* filesystem events */
EVFILT_USER = -10, /* User events */ EVFILT_USER = -10, /* User events */
EVFILT_VM = -12, /* virtual memory events */ EVFILT_VM = -12, /* virtual memory events */
EVFILT_SYSCOUNT = 11 EVFILT_SYSCOUNT = 11
} }
struct kevent_t struct kevent_t
{ {
uintptr_t ident; /* identifier for this event */ uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */ short filter; /* filter for event */
ushort flags; ushort flags;
uint fflags; uint fflags;
intptr_t data; intptr_t data;
void *udata; /* opaque user data identifier */ void *udata; /* opaque user data identifier */
} }
enum enum
{ {
/* actions */ /* actions */
EV_ADD = 0x0001, /* add event to kq (implies enable) */ EV_ADD = 0x0001, /* add event to kq (implies enable) */
EV_DELETE = 0x0002, /* delete event from kq */ EV_DELETE = 0x0002, /* delete event from kq */
EV_ENABLE = 0x0004, /* enable event */ EV_ENABLE = 0x0004, /* enable event */
EV_DISABLE = 0x0008, /* disable event (not reported) */ EV_DISABLE = 0x0008, /* disable event (not reported) */
/* flags */ /* flags */
EV_ONESHOT = 0x0010, /* only report one occurrence */ EV_ONESHOT = 0x0010, /* only report one occurrence */
EV_CLEAR = 0x0020, /* clear event state after reporting */ EV_CLEAR = 0x0020, /* clear event state after reporting */
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */ EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
EV_DISPATCH = 0x0080, /* disable event after reporting */ EV_DISPATCH = 0x0080, /* disable event after reporting */
EV_SYSFLAGS = 0xF000, /* reserved by system */ EV_SYSFLAGS = 0xF000, /* reserved by system */
EV_FLAG1 = 0x2000, /* filter-specific flag */ EV_FLAG1 = 0x2000, /* filter-specific flag */
/* returned values */ /* returned values */
EV_EOF = 0x8000, /* EOF detected */ EV_EOF = 0x8000, /* EOF detected */
EV_ERROR = 0x4000, /* error, data contains errno */ EV_ERROR = 0x4000, /* error, data contains errno */
} }
extern(C) int kqueue() nothrow @nogc; extern(C) int kqueue() nothrow @nogc;
@ -115,211 +115,211 @@ extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
final class KqueueLoop : SelectorLoop final class KqueueLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private Vector!kevent_t events; private Array!kevent_t events;
private Vector!kevent_t changes; private Array!kevent_t changes;
private size_t changeCount; private size_t changeCount;
/** /**
* Returns: Maximal event count can be got at a time * Returns: Maximal event count can be got at a time
* (should be supported by the backend). * (should be supported by the backend).
*/ */
override protected @property uint maxEvents() override protected @property uint maxEvents()
const pure nothrow @safe @nogc const pure nothrow @safe @nogc
{ {
return cast(uint) events.length; return cast(uint) events.length;
} }
this() @nogc this() @nogc
{ {
super(); super();
if ((fd = kqueue()) == -1) if ((fd = kqueue()) == -1)
{ {
throw make!BadLoopException(defaultAllocator, throw make!BadLoopException(defaultAllocator,
"kqueue initialization failed"); "kqueue initialization failed");
} }
events = Vector!kevent_t(64, MmapPool.instance); events = Array!kevent_t(64, MmapPool.instance);
changes = Vector!kevent_t(64, MmapPool.instance); changes = Array!kevent_t(64, MmapPool.instance);
} }
/** /**
* Frees loop internals. * Frees loop internals.
*/ */
~this() @nogc ~this() @nogc
{ {
close(fd); close(fd);
} }
private void set(socket_t socket, short filter, ushort flags) @nogc private void set(SocketType socket, short filter, ushort flags) @nogc
{ {
if (changes.length <= changeCount) if (changes.length <= changeCount)
{ {
changes.length = changeCount + maxEvents; changes.length = changeCount + maxEvents;
} }
EV_SET(&changes[changeCount], EV_SET(&changes[changeCount],
cast(ulong) socket, cast(ulong) socket,
filter, filter,
flags, flags,
0U, 0U,
0L, 0L,
null); null);
++changeCount; ++changeCount;
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
override protected bool reify(SocketWatcher watcher, override protected bool reify(SocketWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) @nogc EventMask events) @nogc
{ {
if (events != oldEvents) if (events != oldEvents)
{ {
if (oldEvents & Event.read || oldEvents & Event.accept) if (oldEvents & Event.read || oldEvents & Event.accept)
{ {
set(watcher.socket.handle, EVFILT_READ, EV_DELETE); set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
} }
if (oldEvents & Event.write) if (oldEvents & Event.write)
{ {
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE); set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
} }
} }
if (events & (Event.read | events & Event.accept)) if (events & (Event.read | events & Event.accept))
{ {
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE); set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
} }
if (events & Event.write) if (events & Event.write)
{ {
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH); set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
} }
return true; return true;
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
protected override void poll() @nogc protected override void poll() @nogc
{ {
timespec ts; timespec ts;
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec); blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
if (changeCount > maxEvents) if (changeCount > maxEvents)
{ {
events.length = changes.length; events.length = changes.length;
} }
auto eventCount = kevent(fd, auto eventCount = kevent(fd,
changes.get().ptr, changes.get().ptr,
cast(int) changeCount, cast(int) changeCount,
events.get().ptr, events.get().ptr,
maxEvents, maxEvents,
&ts); &ts);
changeCount = 0; changeCount = 0;
if (eventCount < 0) if (eventCount < 0)
{ {
if (errno != EINTR) if (errno != EINTR)
{ {
throw defaultAllocator.make!BadLoopException(); throw defaultAllocator.make!BadLoopException();
} }
return; return;
} }
for (int i; i < eventCount; ++i) for (int i; i < eventCount; ++i)
{ {
assert(connections.length > events[i].ident); assert(connections.length > events[i].ident);
auto transport = cast(StreamTransport) connections[events[i].ident]; auto transport = cast(StreamTransport) connections[events[i].ident];
// If it is a ConnectionWatcher. Accept connections. // If it is a ConnectionWatcher. Accept connections.
if (transport is null) if (transport is null)
{ {
auto connection = cast(ConnectionWatcher) connections[events[i].ident]; auto connection = cast(ConnectionWatcher) connections[events[i].ident];
assert(connection !is null); assert(connection !is null);
acceptConnections(connection); acceptConnections(connection);
} }
else if (events[i].flags & EV_ERROR) else if (events[i].flags & EV_ERROR)
{ {
kill(transport); kill(transport);
} }
else if (events[i].filter == EVFILT_READ) else if (events[i].filter == EVFILT_READ)
{ {
SocketException exception; SocketException exception;
try try
{ {
ptrdiff_t received; ptrdiff_t received;
do do
{ {
received = transport.socket.receive(transport.output[]); received = transport.socket.receive(transport.output[]);
transport.output += received; transport.output += received;
} }
while (received); while (received);
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
} }
if (transport.socket.disconnected) if (transport.socket.disconnected)
{ {
kill(transport, exception); kill(transport, exception);
} }
else if (transport.output.length) else if (transport.output.length)
{ {
pendings.enqueue(transport); pendings.enqueue(transport);
} }
} }
else if (events[i].filter == EVFILT_WRITE) else if (events[i].filter == EVFILT_WRITE)
{ {
transport.writeReady = true; transport.writeReady = true;
if (transport.input.length) if (transport.input.length)
{ {
feed(transport); feed(transport);
} }
} }
} }
} }
/** /**
* Returns: The blocking time. * Returns: The blocking time.
*/ */
override protected @property inout(Duration) blockTime() override protected @property inout(Duration) blockTime()
inout @nogc @safe pure nothrow inout @nogc @safe pure nothrow
{ {
return min(super.blockTime, 1.dur!"seconds"); return min(super.blockTime, 1.dur!"seconds");
} }
/** /**
* If the transport couldn't send the data, the further sending should * If the transport couldn't send the data, the further sending should
* be handled by the event loop. * be handled by the event loop.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Exception thrown on sending. * exception = Exception thrown on sending.
* *
* Returns: $(D_KEYWORD true) if the operation could be successfully * Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the * completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then). * transport will be destroyed then).
*/ */
protected override bool feed(StreamTransport transport, protected override bool feed(StreamTransport transport,
SocketException exception = null) @nogc SocketException exception = null) @nogc
{ {
if (!super.feed(transport, exception)) if (!super.feed(transport, exception))
{ {
return false; return false;
} }
if (!transport.writeReady) if (!transport.writeReady)
{ {
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH); set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
return true; return true;
} }
return false; return false;
} }
} }

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;
@ -27,371 +27,374 @@ import tanya.network.socket;
*/ */
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{ {
private SelectorLoop loop; private SelectorLoop loop;
private SocketException exception; private SocketException exception;
package ReadBuffer!ubyte output; package ReadBuffer!ubyte output;
package WriteBuffer!ubyte input; package WriteBuffer!ubyte input;
private Protocol protocol_; private Protocol protocol_;
private bool closing; private bool closing;
/// Received notification that the underlying socket is write-ready. /// Received notification that the underlying socket is write-ready.
package bool writeReady; package bool writeReady;
/** /**
* Params: * Params:
* loop = Event loop. * loop = Event loop.
* socket = Socket. * socket = Socket.
* *
* Precondition: $(D_INLINECODE loop !is null && socket !is null) * Precondition: $(D_INLINECODE loop !is null && socket !is null)
*/ */
this(SelectorLoop loop, ConnectedSocket socket) @nogc this(SelectorLoop loop, ConnectedSocket socket) @nogc
in in
{ {
assert(loop !is null); assert(loop !is null);
} }
body body
{ {
super(socket); super(socket);
this.loop = loop; this.loop = loop;
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance); input = WriteBuffer!ubyte(8192, MmapPool.instance);
active = true; active = true;
} }
/** /**
* Returns: Socket. * Returns: Socket.
* *
* Postcondition: $(D_INLINECODE socket !is null) * Postcondition: $(D_INLINECODE socket !is null)
*/ */
override @property ConnectedSocket socket() pure nothrow @safe @nogc override @property ConnectedSocket socket() pure nothrow @safe @nogc
out (socket) out (socket)
{ {
assert(socket !is null); assert(socket !is null);
} }
body body
{ {
return cast(ConnectedSocket) socket_; return cast(ConnectedSocket) socket_;
} }
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc private @property void socket(ConnectedSocket socket)
in pure nothrow @safe @nogc
{ in
assert(socket !is null); {
} assert(socket !is null);
body }
{ body
socket_ = socket; {
} socket_ = socket;
}
/** /**
* Returns: Application protocol. * Returns: Application protocol.
*/ */
@property Protocol protocol() pure nothrow @safe @nogc @property Protocol protocol() pure nothrow @safe @nogc
{ {
return protocol_; return protocol_;
} }
/** /**
* Switches the protocol. * Switches the protocol.
* *
* The protocol is deallocated by the event loop, it should currently be * The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool). * allocated with $(D_PSYMBOL MmapPool).
* *
* Params: * Params:
* protocol = Application protocol. * protocol = Application protocol.
* *
* Precondition: $(D_INLINECODE protocol !is null) * Precondition: $(D_INLINECODE protocol !is null)
*/ */
@property void protocol(Protocol protocol) pure nothrow @safe @nogc @property void protocol(Protocol protocol) pure nothrow @safe @nogc
in in
{ {
assert(protocol !is null); assert(protocol !is null);
} }
body body
{ {
protocol_ = protocol; protocol_ = protocol;
} }
/** /**
* Returns $(D_PARAM true) if the transport is closing or closed. * Returns $(D_PARAM true) if the transport is closing or closed.
*/ */
bool isClosing() const pure nothrow @safe @nogc bool isClosing() const pure nothrow @safe @nogc
{ {
return closing; return closing;
} }
/** /**
* Close the transport. * Close the transport.
* *
* Buffered data will be flushed. No more data will be received. * Buffered data will be flushed. No more data will be received.
*/ */
void close() @nogc void close() @nogc
{ {
closing = true; closing = true;
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write)); loop.reify(this,
} EventMask(Event.read, Event.write),
EventMask(Event.write));
}
/** /**
* Invokes the watcher callback. * Invokes the watcher callback.
*/ */
override void invoke() @nogc override void invoke() @nogc
{ {
if (output.length) if (output.length)
{ {
protocol.received(output[0 .. $]); protocol.received(output[0 .. $]);
output.clear(); output.clear();
if (isClosing() && input.length == 0) if (isClosing() && input.length == 0)
{ {
loop.kill(this); loop.kill(this);
} }
} }
else else
{ {
protocol.disconnected(exception); protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_); MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(exception); defaultAllocator.dispose(exception);
active = false; active = false;
} }
} }
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* Params: * Params:
* data = Data to send. * data = Data to send.
*/ */
void write(ubyte[] data) @nogc void write(ubyte[] data) @nogc
{ {
if (!data.length) if (!data.length)
{ {
return; return;
} }
// Try to write if the socket is write ready. // Try to write if the socket is write ready.
if (writeReady) if (writeReady)
{ {
ptrdiff_t sent; ptrdiff_t sent;
SocketException exception; SocketException exception;
try try
{ {
sent = socket.send(data); sent = socket.send(data);
if (sent == 0) if (sent == 0)
{ {
writeReady = false; writeReady = false;
} }
} }
catch (SocketException e) catch (SocketException e)
{ {
writeReady = false; writeReady = false;
exception = e; exception = e;
} }
if (sent < data.length) if (sent < data.length)
{ {
input ~= data[sent..$]; input ~= data[sent..$];
loop.feed(this, exception); loop.feed(this, exception);
} }
} }
else else
{ {
input ~= data; input ~= data;
} }
} }
} }
abstract class SelectorLoop : Loop abstract class SelectorLoop : Loop
{ {
/// Pending connections. /// Pending connections.
protected Vector!SocketWatcher connections; protected Array!SocketWatcher connections;
this() @nogc this() @nogc
{ {
super(); super();
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance); connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
} }
~this() @nogc ~this() @nogc
{ {
foreach (ref connection; connections) foreach (ref connection; connections)
{ {
// We want to free only the transports. ConnectionWatcher are created by the // We want to free only the transports. ConnectionWatcher are
// user and should be freed by himself. // created by the user and should be freed by himself.
if (cast(StreamTransport) connection !is null) if (cast(StreamTransport) connection !is null)
{ {
MmapPool.instance.dispose(connection); MmapPool.instance.dispose(connection);
} }
} }
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
override abstract protected bool reify(SocketWatcher watcher, override abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) @nogc; EventMask events) @nogc;
/** /**
* Kills the watcher and closes the connection. * Kills the watcher and closes the connection.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Occurred exception. * exception = Occurred exception.
*/ */
protected void kill(StreamTransport transport, protected void kill(StreamTransport transport,
SocketException exception = null) @nogc SocketException exception = null) @nogc
in in
{ {
assert(transport !is null); assert(transport !is null);
} }
body body
{ {
transport.socket.shutdown(); transport.socket.shutdown();
defaultAllocator.dispose(transport.socket); defaultAllocator.dispose(transport.socket);
transport.exception = exception; transport.exception = exception;
pendings.enqueue(transport); pendings.enqueue(transport);
} }
/** /**
* If the transport couldn't send the data, the further sending should * If the transport couldn't send the data, the further sending should
* be handled by the event loop. * be handled by the event loop.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Exception thrown on sending. * exception = Exception thrown on sending.
* *
* Returns: $(D_KEYWORD true) if the operation could be successfully * Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the * completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then). * transport will be destroyed then).
*/ */
protected bool feed(StreamTransport transport, protected bool feed(StreamTransport transport,
SocketException exception = null) @nogc SocketException exception = null) @nogc
in in
{ {
assert(transport !is null); assert(transport !is null);
} }
body body
{ {
while (transport.input.length && transport.writeReady) while (transport.input.length && transport.writeReady)
{ {
try try
{ {
ptrdiff_t sent = transport.socket.send(transport.input[]); ptrdiff_t sent = transport.socket.send(transport.input[]);
if (sent == 0) if (sent == 0)
{ {
transport.writeReady = false; transport.writeReady = false;
} }
else else
{ {
transport.input += sent; transport.input += sent;
} }
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
transport.writeReady = false; transport.writeReady = false;
} }
} }
if (exception !is null) if (exception !is null)
{ {
kill(transport, exception); kill(transport, exception);
return false; return false;
} }
if (transport.input.length == 0 && transport.isClosing()) if (transport.input.length == 0 && transport.isClosing())
{ {
kill(transport); kill(transport);
} }
return true; return true;
} }
/** /**
* Start watching. * Start watching.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
*/ */
override void start(ConnectionWatcher watcher) @nogc override void start(ConnectionWatcher watcher) @nogc
{ {
if (watcher.active) if (watcher.active)
{ {
return; return;
} }
if (connections.length <= watcher.socket) if (connections.length <= watcher.socket)
{ {
connections.length = watcher.socket.handle + maxEvents / 2; connections.length = watcher.socket.handle + maxEvents / 2;
} }
connections[watcher.socket.handle] = watcher; connections[watcher.socket.handle] = watcher;
super.start(watcher); super.start(watcher);
} }
/** /**
* Accept incoming connections. * Accept incoming connections.
* *
* Params: * Params:
* connection = Connection watcher ready to accept. * connection = Connection watcher ready to accept.
*/ */
package void acceptConnections(ConnectionWatcher connection) @nogc package void acceptConnections(ConnectionWatcher connection) @nogc
in in
{ {
assert(connection !is null); assert(connection !is null);
} }
body body
{ {
while (true) while (true)
{ {
ConnectedSocket client; ConnectedSocket client;
try try
{ {
client = (cast(StreamSocket) connection.socket).accept(); client = (cast(StreamSocket) connection.socket).accept();
} }
catch (SocketException e) catch (SocketException e)
{ {
defaultAllocator.dispose(e); defaultAllocator.dispose(e);
break; break;
} }
if (client is null) if (client is null)
{ {
break; break;
} }
StreamTransport transport; StreamTransport transport;
if (connections.length > client.handle) if (connections.length > client.handle)
{ {
transport = cast(StreamTransport) connections[client.handle]; transport = cast(StreamTransport) connections[client.handle];
} }
else else
{ {
connections.length = client.handle + maxEvents / 2; connections.length = client.handle + maxEvents / 2;
} }
if (transport is null) if (transport is null)
{ {
transport = MmapPool.instance.make!StreamTransport(this, client); transport = MmapPool.instance.make!StreamTransport(this, client);
connections[client.handle] = transport; connections[client.handle] = transport;
} }
else else
{ {
transport.socket = client; transport.socket = client;
} }
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write)); reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.enqueue(transport); connection.incoming.enqueue(transport);
} }
if (!connection.incoming.empty) if (!connection.incoming.empty)
{ {
pendings.enqueue(connection); pendings.enqueue(connection);
} }
} }
} }

View File

@ -21,12 +21,12 @@ import core.sys.windows.windef;
*/ */
class State class State
{ {
/// For internal use by Windows API. /// For internal use by Windows API.
align(1) OVERLAPPED overlapped; align(1) OVERLAPPED overlapped;
/// File/socket handle. /// File/socket handle.
HANDLE handle; HANDLE handle;
/// For keeping events or event masks. /// For keeping events or event masks.
int event; int event;
} }

View File

@ -15,50 +15,50 @@
* *
* class EchoProtocol : TransmissionControlProtocol * class EchoProtocol : TransmissionControlProtocol
* { * {
* private DuplexTransport transport; * private DuplexTransport transport;
* *
* void received(in ubyte[] data) @nogc * void received(in ubyte[] data) @nogc
* { * {
* transport.write(data); * transport.write(data);
* } * }
* *
* void connected(DuplexTransport transport) @nogc * void connected(DuplexTransport transport) @nogc
* { * {
* this.transport = transport; * this.transport = transport;
* } * }
* *
* void disconnected(SocketException e) @nogc * void disconnected(SocketException e) @nogc
* { * {
* } * }
* } * }
* *
* void main() * void main()
* { * {
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192); * auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
* *
* version (Windows) * version (Windows)
* { * {
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET); * auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
* } * }
* else * else
* { * {
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET); * auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* sock.blocking = false; * sock.blocking = false;
* } * }
* *
* sock.bind(address); * sock.bind(address);
* sock.listen(5); * sock.listen(5);
* *
* auto io = defaultAllocator.make!ConnectionWatcher(sock); * auto io = defaultAllocator.make!ConnectionWatcher(sock);
* io.setProtocol!EchoProtocol; * io.setProtocol!EchoProtocol;
* *
* defaultLoop.start(io); * defaultLoop.start(io);
* defaultLoop.run(); * defaultLoop.run();
* *
* sock.shutdown(); * sock.shutdown();
* defaultAllocator.dispose(io); * defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock); * defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address); * defaultAllocator.dispose(address);
* } * }
* --- * ---
*/ */
@ -81,33 +81,33 @@ version (DisableBackends)
} }
else version (linux) else version (linux)
{ {
import tanya.async.event.epoll; import tanya.async.event.epoll;
version = Epoll; version = Epoll;
} }
else version (Windows) else version (Windows)
{ {
import tanya.async.event.iocp; import tanya.async.event.iocp;
version = IOCP; version = IOCP;
} }
else version (OSX) else version (OSX)
{ {
version = Kqueue; version = Kqueue;
} }
else version (iOS) else version (iOS)
{ {
version = Kqueue; version = Kqueue;
} }
else version (FreeBSD) else version (FreeBSD)
{ {
version = Kqueue; version = Kqueue;
} }
else version (OpenBSD) else version (OpenBSD)
{ {
version = Kqueue; version = Kqueue;
} }
else version (DragonFlyBSD) else version (DragonFlyBSD)
{ {
version = Kqueue; version = Kqueue;
} }
/** /**
@ -115,11 +115,11 @@ else version (DragonFlyBSD)
*/ */
enum Event : uint enum Event : uint
{ {
none = 0x00, /// No events. none = 0x00, /// No events.
read = 0x01, /// Non-blocking read call. read = 0x01, /// Non-blocking read call.
write = 0x02, /// Non-blocking write call. write = 0x02, /// Non-blocking write call.
accept = 0x04, /// Connection made. accept = 0x04, /// Connection made.
error = 0x80000000, /// Sent when an error occurs. error = 0x80000000, /// Sent when an error occurs.
} }
alias EventMask = BitFlags!Event; alias EventMask = BitFlags!Event;
@ -129,150 +129,150 @@ alias EventMask = BitFlags!Event;
*/ */
abstract class Loop abstract class Loop
{ {
private bool done; private bool done;
/// Pending watchers. /// Pending watchers.
protected Queue!Watcher pendings; protected Queue!Watcher pendings;
/** /**
* Returns: Maximal event count can be got at a time * Returns: Maximal event count can be got at a time
* (should be supported by the backend). * (should be supported by the backend).
*/ */
protected @property uint maxEvents() protected @property uint maxEvents()
const pure nothrow @safe @nogc const pure nothrow @safe @nogc
{ {
return 128U; return 128U;
} }
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
this() @nogc this() @nogc
{ {
pendings = Queue!Watcher(MmapPool.instance); pendings = Queue!Watcher(MmapPool.instance);
} }
/** /**
* Frees loop internals. * Frees loop internals.
*/ */
~this() @nogc ~this() @nogc
{ {
foreach (w; pendings) foreach (w; pendings)
{ {
MmapPool.instance.dispose(w); MmapPool.instance.dispose(w);
} }
} }
/** /**
* Starts the loop. * Starts the loop.
*/ */
void run() @nogc void run() @nogc
{ {
done = false; done = false;
do do
{ {
poll(); poll();
// Invoke pendings // Invoke pendings
foreach (ref w; pendings) foreach (ref w; pendings)
{ {
w.invoke(); w.invoke();
} }
} }
while (!done); while (!done);
} }
/** /**
* Break out of the loop. * Break out of the loop.
*/ */
void unloop() @safe pure nothrow @nogc void unloop() @safe pure nothrow @nogc
{ {
done = true; done = true;
} }
/** /**
* Start watching. * Start watching.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
*/ */
void start(ConnectionWatcher watcher) @nogc void start(ConnectionWatcher watcher) @nogc
{ {
if (watcher.active) if (watcher.active)
{ {
return; return;
} }
watcher.active = true; watcher.active = true;
reify(watcher, EventMask(Event.none), EventMask(Event.accept)); reify(watcher, EventMask(Event.none), EventMask(Event.accept));
} }
/** /**
* Stop watching. * Stop watching.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
*/ */
void stop(ConnectionWatcher watcher) @nogc void stop(ConnectionWatcher watcher) @nogc
{ {
if (!watcher.active) if (!watcher.active)
{ {
return; return;
} }
watcher.active = false; watcher.active = false;
reify(watcher, EventMask(Event.accept), EventMask(Event.none)); reify(watcher, EventMask(Event.accept), EventMask(Event.none));
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
abstract protected bool reify(SocketWatcher watcher, abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) @nogc; EventMask events) @nogc;
/** /**
* Returns: The blocking time. * Returns: The blocking time.
*/ */
protected @property inout(Duration) blockTime() protected @property inout(Duration) blockTime()
inout @safe pure nothrow @nogc inout @safe pure nothrow @nogc
{ {
// Don't block if we have to do. // Don't block if we have to do.
return pendings.empty ? blockTime_ : Duration.zero; return pendings.empty ? blockTime_ : Duration.zero;
} }
/** /**
* Sets the blocking time for IO watchers. * Sets the blocking time for IO watchers.
* *
* Params: * Params:
* blockTime = The blocking time. Cannot be larger than * blockTime = The blocking time. Cannot be larger than
* $(D_PSYMBOL maxBlockTime). * $(D_PSYMBOL maxBlockTime).
*/ */
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
in in
{ {
assert(blockTime <= 1.dur!"hours", "Too long to wait."); assert(blockTime <= 1.dur!"hours", "Too long to wait.");
assert(!blockTime.isNegative); assert(!blockTime.isNegative);
} }
body body
{ {
blockTime_ = blockTime; blockTime_ = blockTime;
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
abstract protected void poll() @nogc; abstract protected void poll() @nogc;
/// Maximal block time. /// Maximal block time.
protected Duration blockTime_ = 1.dur!"minutes"; protected Duration blockTime_ = 1.dur!"minutes";
} }
/** /**
@ -280,17 +280,17 @@ abstract class Loop
*/ */
class BadLoopException : Exception class BadLoopException : Exception
{ {
/** /**
* Params: * Params:
* file = The file where the exception occurred. * file = The file where the exception occurred.
* line = The line number where the exception occurred. * line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any. * next = The previous exception in the chain of exceptions, if any.
*/ */
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
pure nothrow const @safe @nogc pure nothrow const @safe @nogc
{ {
super("Event loop cannot be initialized.", file, line, next); super("Event loop cannot be initialized.", file, line, next);
} }
} }
/** /**
@ -302,24 +302,24 @@ class BadLoopException : Exception
*/ */
@property Loop defaultLoop() @nogc @property Loop defaultLoop() @nogc
{ {
if (defaultLoop_ !is null) if (defaultLoop_ !is null)
{ {
return defaultLoop_; return defaultLoop_;
} }
version (Epoll) version (Epoll)
{ {
defaultLoop_ = MmapPool.instance.make!EpollLoop; defaultLoop_ = MmapPool.instance.make!EpollLoop;
} }
else version (IOCP) else version (IOCP)
{ {
defaultLoop_ = MmapPool.instance.make!IOCPLoop; defaultLoop_ = MmapPool.instance.make!IOCPLoop;
} }
else version (Kqueue) else version (Kqueue)
{ {
import tanya.async.event.kqueue; import tanya.async.event.kqueue;
defaultLoop_ = MmapPool.instance.make!KqueueLoop; defaultLoop_ = MmapPool.instance.make!KqueueLoop;
} }
return defaultLoop_; return defaultLoop_;
} }
/** /**
@ -331,16 +331,16 @@ class BadLoopException : Exception
* your implementation to this property. * your implementation to this property.
* *
* Params: * Params:
* loop = The event loop. * loop = The event loop.
*/ */
@property void defaultLoop(Loop loop) @nogc @property void defaultLoop(Loop loop) @nogc
in in
{ {
assert(loop !is null); assert(loop !is null);
} }
body body
{ {
defaultLoop_ = loop; defaultLoop_ = loop;
} }
private Loop defaultLoop_; private Loop defaultLoop_;

View File

@ -18,28 +18,28 @@ import tanya.async.transport;
*/ */
interface Protocol interface Protocol
{ {
/** /**
* Params: * Params:
* data = Read data. * data = Read data.
*/ */
void received(in ubyte[] data) @nogc; void received(in ubyte[] data) @nogc;
/** /**
* Called when a connection is made. * Called when a connection is made.
* *
* Params: * Params:
* transport = Protocol transport. * transport = Protocol transport.
*/ */
void connected(DuplexTransport transport) @nogc; void connected(DuplexTransport transport) @nogc;
/** /**
* Called when a connection is lost. * Called when a connection is lost.
* *
* Params: * Params:
* exception = $(D_PSYMBOL Exception) if an error caused * exception = $(D_PSYMBOL Exception) if an error caused
* the disconnect, $(D_KEYWORD null) otherwise. * the disconnect, $(D_KEYWORD null) otherwise.
*/ */
void disconnected(SocketException exception) @nogc; void disconnected(SocketException exception) @nogc;
} }
/** /**

View File

@ -32,13 +32,13 @@ interface ReadTransport : Transport
*/ */
interface WriteTransport : Transport interface WriteTransport : Transport
{ {
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* Params: * Params:
* data = Data to send. * data = Data to send.
*/ */
void write(ubyte[] data) @nogc; void write(ubyte[] data) @nogc;
} }
/** /**
@ -46,46 +46,46 @@ interface WriteTransport : Transport
*/ */
interface DuplexTransport : ReadTransport, WriteTransport interface DuplexTransport : ReadTransport, WriteTransport
{ {
/** /**
* Returns: Application protocol. * Returns: Application protocol.
* *
* Postcondition: $(D_INLINECODE protocol !is null) * Postcondition: $(D_INLINECODE protocol !is null)
*/ */
@property Protocol protocol() pure nothrow @safe @nogc @property Protocol protocol() pure nothrow @safe @nogc
out (protocol) out (protocol)
{ {
assert(protocol !is null); assert(protocol !is null);
} }
/** /**
* Switches the protocol. * Switches the protocol.
* *
* The protocol is deallocated by the event loop, it should currently be * The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool). * allocated with $(D_PSYMBOL MmapPool).
* *
* Params: * Params:
* protocol = Application protocol. * protocol = Application protocol.
* *
* Precondition: $(D_INLINECODE protocol !is null) * Precondition: $(D_INLINECODE protocol !is null)
*/ */
@property void protocol(Protocol protocol) pure nothrow @safe @nogc @property void protocol(Protocol protocol) pure nothrow @safe @nogc
in in
{ {
assert(protocol !is null); assert(protocol !is null);
} }
/** /**
* Returns $(D_PARAM true) if the transport is closing or closed. * Returns $(D_PARAM true) if the transport is closing or closed.
*/ */
bool isClosing() const pure nothrow @safe @nogc; bool isClosing() const pure nothrow @safe @nogc;
/** /**
* Close the transport. * Close the transport.
* *
* Buffered data will be flushed. No more data will be received. * Buffered data will be flushed. No more data will be received.
*/ */
void close() @nogc; void close() @nogc;
} }
/** /**
@ -93,8 +93,8 @@ interface DuplexTransport : ReadTransport, WriteTransport
*/ */
interface SocketTransport : Transport interface SocketTransport : Transport
{ {
/** /**
* Returns: Socket. * Returns: Socket.
*/ */
@property Socket socket() pure nothrow @safe @nogc; @property Socket socket() pure nothrow @safe @nogc;
} }

View File

@ -27,13 +27,13 @@ import tanya.network.socket;
*/ */
abstract class Watcher abstract class Watcher
{ {
/// Whether the watcher is active. /// Whether the watcher is active.
bool active; bool active;
/** /**
* Invoke some action on event. * Invoke some action on event.
*/ */
void invoke() @nogc; void invoke() @nogc;
} }
/** /**
@ -41,32 +41,32 @@ abstract class Watcher
*/ */
abstract class SocketWatcher : Watcher abstract class SocketWatcher : Watcher
{ {
/// Watched socket. /// Watched socket.
protected Socket socket_; protected Socket socket_;
/** /**
* Params: * Params:
* socket = Socket. * socket = Socket.
* *
* Precondition: $(D_INLINECODE socket !is null) * Precondition: $(D_INLINECODE socket !is null)
*/ */
this(Socket socket) pure nothrow @safe @nogc this(Socket socket) pure nothrow @safe @nogc
in in
{ {
assert(socket !is null); assert(socket !is null);
} }
body body
{ {
socket_ = socket; socket_ = socket;
} }
/** /**
* Returns: Socket. * Returns: Socket.
*/ */
@property Socket socket() pure nothrow @safe @nogc @property Socket socket() pure nothrow @safe @nogc
{ {
return socket_; return socket_;
} }
} }
/** /**
@ -74,44 +74,44 @@ abstract class SocketWatcher : Watcher
*/ */
class ConnectionWatcher : SocketWatcher class ConnectionWatcher : SocketWatcher
{ {
/// Incoming connection queue. /// Incoming connection queue.
Queue!DuplexTransport incoming; Queue!DuplexTransport incoming;
private Protocol delegate() @nogc protocolFactory; private Protocol delegate() @nogc protocolFactory;
/** /**
* Params: * Params:
* socket = Socket. * socket = Socket.
*/ */
this(Socket socket) @nogc this(Socket socket) @nogc
{ {
super(socket); super(socket);
incoming = Queue!DuplexTransport(MmapPool.instance); incoming = Queue!DuplexTransport(MmapPool.instance);
} }
/** /**
* Params: * Params:
* P = Protocol should be used. * P = Protocol should be used.
*/ */
void setProtocol(P : Protocol)() @nogc void setProtocol(P : Protocol)() @nogc
{ {
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P; this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
} }
/** /**
* Invokes new connection callback. * Invokes new connection callback.
*/ */
override void invoke() @nogc override void invoke() @nogc
in in
{ {
assert(protocolFactory !is null, "Protocol isn't set."); assert(protocolFactory !is null, "Protocol isn't set.");
} }
body body
{ {
foreach (transport; incoming) foreach (transport; incoming)
{ {
transport.protocol = protocolFactory(); transport.protocol = protocolFactory();
transport.protocol.connected(transport); transport.protocol.connected(transport);
} }
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,14 +9,103 @@
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/ */
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

@ -3,14 +3,40 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Abstract data types whose instances are collections of other objects.
*
* Copyright: Eugene Wissner 2016-2017. * 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/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/ */
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

@ -3,11 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* FIFO queue.
*
* 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/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/ */
module tanya.container.queue; module tanya.container.queue;
import core.exception; import core.exception;
@ -20,267 +22,267 @@ import tanya.memory;
* FIFO queue. * FIFO queue.
* *
* Params: * Params:
* T = Content type. * T = Content type.
*/ */
struct Queue(T) struct Queue(T)
{ {
/** /**
* Removes all elements from the queue. * Removes all elements from the queue.
*/ */
~this() ~this()
{ {
while (!empty) while (!empty)
{ {
dequeue(); dequeue();
} }
} }
/** /**
* Returns how many elements are in the queue. It iterates through the queue * Returns how many elements are in the queue. It iterates through the queue
* to count the elements. * to count the elements.
* *
* Returns: How many elements are in the queue. * Returns: How many elements are in the queue.
*/ */
size_t length() const size_t length() const
{ {
size_t len; size_t len;
for (const(SEntry!T)* i = first; i !is null; i = i.next) for (const(SEntry!T)* i = first; i !is null; i = i.next)
{ {
++len; ++len;
} }
return len; return len;
} }
/// ///
unittest unittest
{ {
Queue!int q; Queue!int q;
assert(q.length == 0); assert(q.length == 0);
q.enqueue(5); q.enqueue(5);
assert(q.length == 1); assert(q.length == 1);
q.enqueue(4); q.enqueue(4);
assert(q.length == 2); assert(q.length == 2);
q.enqueue(9); q.enqueue(9);
assert(q.length == 3); assert(q.length == 3);
q.dequeue(); q.dequeue();
assert(q.length == 2); assert(q.length == 2);
q.dequeue(); q.dequeue();
assert(q.length == 1); assert(q.length == 1);
q.dequeue(); q.dequeue();
assert(q.length == 0); assert(q.length == 0);
} }
private void enqueueEntry(ref SEntry!T* entry) private void enqueueEntry(ref SEntry!T* entry)
{ {
if (empty) if (empty)
{ {
first = rear = entry; first = rear = entry;
} }
else else
{ {
rear.next = entry; rear.next = entry;
rear = rear.next; rear = rear.next;
} }
} }
private SEntry!T* allocateEntry() private SEntry!T* allocateEntry()
{ {
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof); auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
if (temp is null) if (temp is null)
{ {
onOutOfMemoryError(); onOutOfMemoryError();
} }
return temp; return temp;
} }
/** /**
* Inserts a new element. * Inserts a new element.
* *
* Params: * Params:
* x = New element. * x = New element.
*/ */
void enqueue(ref T x) void enqueue(ref T x)
{ {
auto temp = allocateEntry(); auto temp = allocateEntry();
*temp = SEntry!T.init; *temp = SEntry!T.init;
temp.content = x; temp.content = x;
enqueueEntry(temp); enqueueEntry(temp);
} }
/// Ditto. /// Ditto.
void enqueue(T x) void enqueue(T x)
{ {
auto temp = allocateEntry(); auto temp = allocateEntry();
moveEmplace(x, (*temp).content); moveEmplace(x, (*temp).content);
(*temp).next = null; (*temp).next = null;
enqueueEntry(temp); enqueueEntry(temp);
} }
/// ///
unittest unittest
{ {
Queue!int q; Queue!int q;
assert(q.empty); assert(q.empty);
q.enqueue(8); q.enqueue(8);
q.enqueue(9); q.enqueue(9);
assert(q.dequeue() == 8); assert(q.dequeue() == 8);
assert(q.dequeue() == 9); assert(q.dequeue() == 9);
} }
/** /**
* Returns: $(D_KEYWORD true) if the queue is empty. * Returns: $(D_KEYWORD true) if the queue is empty.
*/ */
@property bool empty() const @property bool empty() const
{ {
return first is null; return first is null;
} }
/// ///
unittest unittest
{ {
Queue!int q; Queue!int q;
int value = 7; int value = 7;
assert(q.empty); assert(q.empty);
q.enqueue(value); q.enqueue(value);
assert(!q.empty); assert(!q.empty);
} }
/** /**
* Move the position to the next element. * Move the position to the next element.
* *
* Returns: Dequeued element. * Returns: Dequeued element.
*/ */
T dequeue() T dequeue()
in in
{ {
assert(!empty); assert(!empty);
} }
body body
{ {
auto n = first.next; auto n = first.next;
T ret = move(first.content); T ret = move(first.content);
allocator.dispose(first); allocator.dispose(first);
first = n; first = n;
return ret; return ret;
} }
/// ///
unittest unittest
{ {
Queue!int q; Queue!int q;
q.enqueue(8); q.enqueue(8);
q.enqueue(9); q.enqueue(9);
assert(q.dequeue() == 8); assert(q.dequeue() == 8);
assert(q.dequeue() == 9); assert(q.dequeue() == 9);
} }
/** /**
* $(D_KEYWORD foreach) iteration. The elements will be automatically * $(D_KEYWORD foreach) iteration. The elements will be automatically
* dequeued. * dequeued.
* *
* Params: * Params:
* dg = $(D_KEYWORD foreach) body. * dg = $(D_KEYWORD foreach) body.
* *
* Returns: The value returned from $(D_PARAM dg). * Returns: The value returned from $(D_PARAM dg).
*/ */
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg) int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{ {
int result; int result;
for (size_t i = 0; !empty; ++i) for (size_t i = 0; !empty; ++i)
{ {
auto e = dequeue(); auto e = dequeue();
if ((result = dg(i, e)) != 0) if ((result = dg(i, e)) != 0)
{ {
return result; return result;
} }
} }
return result; return result;
} }
/// Ditto. /// Ditto.
int opApply(scope int delegate(ref T) @nogc dg) int opApply(scope int delegate(ref T) @nogc dg)
{ {
int result; int result;
while (!empty) while (!empty)
{ {
auto e = dequeue(); auto e = dequeue();
if ((result = dg(e)) != 0) if ((result = dg(e)) != 0)
{ {
return result; return result;
} }
} }
return result; return result;
} }
/// ///
unittest unittest
{ {
Queue!int q; Queue!int q;
size_t j; size_t j;
q.enqueue(5); q.enqueue(5);
q.enqueue(4); q.enqueue(4);
q.enqueue(9); q.enqueue(9);
foreach (i, e; q) foreach (i, e; q)
{ {
assert(i != 2 || e == 9); assert(i != 2 || e == 9);
assert(i != 1 || e == 4); assert(i != 1 || e == 4);
assert(i != 0 || e == 5); assert(i != 0 || e == 5);
++j; ++j;
} }
assert(j == 3); assert(j == 3);
assert(q.empty); assert(q.empty);
j = 0; j = 0;
q.enqueue(5); q.enqueue(5);
q.enqueue(4); q.enqueue(4);
q.enqueue(9); q.enqueue(9);
foreach (e; q) foreach (e; q)
{ {
assert(j != 2 || e == 9); assert(j != 2 || e == 9);
assert(j != 1 || e == 4); assert(j != 1 || e == 4);
assert(j != 0 || e == 5); assert(j != 0 || e == 5);
++j; ++j;
} }
assert(j == 3); assert(j == 3);
assert(q.empty); assert(q.empty);
} }
private SEntry!T* first; private SEntry!T* first;
private SEntry!T* rear; private SEntry!T* rear;
mixin DefaultAllocator; mixin DefaultAllocator;
} }
/// ///
unittest unittest
{ {
Queue!int q; Queue!int q;
q.enqueue(5); q.enqueue(5);
assert(!q.empty); assert(!q.empty);
q.enqueue(4); q.enqueue(4);
q.enqueue(9); q.enqueue(9);
assert(q.dequeue() == 5); assert(q.dequeue() == 5);
foreach (i, ref e; q) foreach (i, ref e; q)
{ {
assert(i != 0 || e == 4); assert(i != 0 || e == 4);
assert(i != 1 || e == 9); assert(i != 1 || e == 9);
} }
assert(q.empty); assert(q.empty);
} }

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

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/ */
module tanya.math; module tanya.math;
import std.traits; import std.traits;
@ -16,7 +16,7 @@ public import tanya.math.random;
version (unittest) version (unittest)
{ {
import std.algorithm.iteration; import std.algorithm.iteration;
} }
/** /**
@ -26,12 +26,12 @@ version (unittest)
* is used to allocate the result. * is used to allocate the result.
* *
* Params: * Params:
* I = Base type. * I = Base type.
* G = Exponent type. * G = Exponent type.
* H = Divisor type: * H = Divisor type:
* x = Base. * x = Base.
* y = Exponent. * y = Exponent.
* z = Divisor. * z = Divisor.
* *
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y) * Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
* by $(D_PARAM z). * by $(D_PARAM z).
@ -39,134 +39,163 @@ version (unittest)
* Precondition: $(D_INLINECODE z > 0) * Precondition: $(D_INLINECODE z > 0)
*/ */
H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z) H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
if (isIntegral!I && isIntegral!G && isIntegral!H) if (isIntegral!I && isIntegral!G && isIntegral!H)
in in
{ {
assert(z > 0, "Division by zero."); assert(z > 0, "Division by zero.");
} }
body body
{ {
G mask = G.max / 2 + 1; G mask = G.max / 2 + 1;
H result; H result;
if (y == 0) if (y == 0)
{ {
return 1 % z; return 1 % z;
} }
else if (y == 1) else if (y == 1)
{ {
return x % z; return x % z;
} }
do do
{ {
immutable bit = y & mask; immutable bit = y & mask;
if (!result && bit) if (!result && bit)
{ {
result = x; result = x;
continue; continue;
} }
result *= result; result *= result;
if (bit) if (bit)
{ {
result *= x; result *= x;
} }
result %= z; result %= z;
} }
while (mask >>= 1); while (mask >>= 1);
return result; return result;
} }
/// Ditto. /// Ditto.
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z) I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
if (is(I == Integer)) if (is(I == Integer))
in in
{ {
assert(z.length > 0, "Division by zero."); assert(z.length > 0, "Division by zero.");
} }
body body
{ {
size_t i = y.length; size_t i;
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator); auto tmp1 = Integer(x, x.allocator);
Integer result = Integer(x.allocator); auto result = Integer(x.allocator);
bool firstBit;
if (x.length == 0 && i != 0) if (x.size == 0 && y.size != 0)
{ {
i = 0; i = y.size;
} }
else else
{ {
result = 1; result = 1;
} }
while (i) while (i < y.size)
{ {
--i; for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
for (ubyte mask = 0x01; mask; mask <<= 1) {
{ if (y.rep[i] & mask)
if (y.rep[i] & mask) {
{ result *= tmp1;
result *= tmp1; result %= z;
result %= z; }
} auto tmp2 = tmp1;
tmp2 = tmp1; tmp1 *= tmp2;
tmp1 *= tmp2; tmp1 %= z;
tmp1 %= z; }
} ++i;
} }
return result; return result;
} }
/// ///
pure nothrow @safe @nogc unittest pure nothrow @safe @nogc unittest
{ {
assert(pow(3, 5, 7) == 5); assert(pow(3, 5, 7) == 5);
assert(pow(2, 2, 1) == 0); assert(pow(2, 2, 1) == 0);
assert(pow(3, 3, 3) == 0); assert(pow(3, 3, 3) == 0);
assert(pow(7, 4, 2) == 1); assert(pow(7, 4, 2) == 1);
assert(pow(53, 0, 2) == 1); assert(pow(53, 0, 2) == 1);
assert(pow(53, 1, 3) == 2); assert(pow(53, 1, 3) == 2);
assert(pow(53, 2, 5) == 4); assert(pow(53, 2, 5) == 4);
assert(pow(0, 0, 5) == 1); assert(pow(0, 0, 5) == 1);
assert(pow(0, 5, 5) == 0); assert(pow(0, 5, 5) == 0);
} }
/// ///
unittest unittest
{ {
assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5); assert(pow(Integer(3), Integer(5), Integer(7)) == 5);
assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0); assert(pow(Integer(2), Integer(2), Integer(1)) == 0);
assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0); assert(pow(Integer(3), Integer(3), Integer(3)) == 0);
assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1); assert(pow(Integer(7), Integer(4), Integer(2)) == 1);
assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1); assert(pow(Integer(53), Integer(0), Integer(2)) == 1);
assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2); assert(pow(Integer(53), Integer(1), Integer(3)) == 2);
assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4); assert(pow(Integer(53), Integer(2), Integer(5)) == 4);
assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1); assert(pow(Integer(0), Integer(0), Integer(5)) == 1);
assert(cast(long) pow(Integer(0), Integer(5), Integer(5)) == 0); assert(pow(Integer(0), Integer(5), Integer(5)) == 0);
} }
/** /**
* Checks if $(D_PARAM x) is a prime. * Checks if $(D_PARAM x) is a prime.
* *
* Params: * Params:
* x = The number should be checked. * x = The number should be checked.
* *
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number, * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
* $(D_KEYWORD false) otherwise. * $(D_KEYWORD false) otherwise.
*/ */
bool isPseudoprime(ulong x) nothrow pure @safe @nogc bool isPseudoprime(ulong x) nothrow pure @safe @nogc
{ {
return pow(2, x - 1, x) == 1; return pow(2, x - 1, x) == 1;
} }
/// ///
unittest unittest
{ {
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821, 74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
74827, 9973, 104729, 15485867, 49979693, 104395303, 74827, 9973, 104729, 15485867, 49979693, 104395303,
593441861, 104729, 15485867, 49979693, 104395303, 593441861, 104729, 15485867, 49979693, 104395303,
593441861, 899809363, 982451653]; 593441861, 899809363, 982451653];
known.each!((ref x) => assert(isPseudoprime(x))); known.each!((ref x) => assert(isPseudoprime(x)));
}
/**
* Params:
* I = Value type.
* x = Value.
*
* Returns: The absolute value of a number.
*/
I abs(I : Integer)(const auto ref I x)
{
auto result = Integer(x, x.allocator);
result.sign = Sign.positive;
return result;
}
/// Ditto.
I abs(I : Integer)(I x)
{
x.sign = Sign.positive;
return x;
}
/// Ditto.
I abs(I)(const I x)
if (isIntegral!I)
{
return x >= 0 ? x : -x;
} }

View File

@ -8,8 +8,8 @@
* Copyright: Eugene Wissner 2016. * Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/ */
module tanya.math.random; module tanya.math.random;
import std.digest.sha; import std.digest.sha;
@ -27,20 +27,20 @@ enum maxGather = 128;
*/ */
class EntropyException : Exception class EntropyException : Exception
{ {
/** /**
* Params: * Params:
* msg = Message to output. * msg = Message to output.
* file = The file where the exception occurred. * file = The file where the exception occurred.
* line = The line number where the exception occurred. * line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any. * next = The previous exception in the chain of exceptions, if any.
*/ */
this(string msg, this(string msg,
string file = __FILE__, string file = __FILE__,
size_t line = __LINE__, size_t line = __LINE__,
Throwable next = null) pure @safe nothrow const @nogc Throwable next = null) pure @safe nothrow const @nogc
{ {
super(msg, file, line, next); super(msg, file, line, next);
} }
} }
/** /**
@ -48,103 +48,103 @@ class EntropyException : Exception
*/ */
abstract class EntropySource abstract class EntropySource
{ {
/// Amount of already generated entropy. /// Amount of already generated entropy.
protected ushort size_; protected ushort size_;
/** /**
* Returns: Minimum bytes required from the entropy source. * Returns: Minimum bytes required from the entropy source.
*/ */
@property immutable(ubyte) threshold() const @safe pure nothrow; @property immutable(ubyte) threshold() const @safe pure nothrow;
/** /**
* Returns: Whether this entropy source is strong. * Returns: Whether this entropy source is strong.
*/ */
@property immutable(bool) strong() const @safe pure nothrow; @property immutable(bool) strong() const @safe pure nothrow;
/** /**
* Returns: Amount of already generated entropy. * Returns: Amount of already generated entropy.
*/ */
@property ushort size() const @safe pure nothrow @property ushort size() const @safe pure nothrow
{ {
return size_; return size_;
} }
/** /**
* Params: * Params:
* size = Amount of already generated entropy. Cannot be smaller than the * size = Amount of already generated entropy. Cannot be smaller than the
* already set value. * already set value.
*/ */
@property void size(ushort size) @safe pure nothrow @property void size(ushort size) @safe pure nothrow
{ {
size_ = size; size_ = size;
} }
/** /**
* Poll the entropy source. * Poll the entropy source.
* *
* Params: * Params:
* output = Buffer to save the generate random sequence (the method will * output = Buffer to save the generate random sequence (the method will
* to fill the buffer). * to fill the buffer).
* *
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error. * or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/ */
Nullable!ubyte poll(out ubyte[maxGather] output); Nullable!ubyte poll(out ubyte[maxGather] output);
} }
version (linux) version (linux)
{ {
extern (C) long syscall(long number, ...) nothrow; extern (C) long syscall(long number, ...) nothrow;
/** /**
* Uses getrandom system call. * Uses getrandom system call.
*/ */
class PlatformEntropySource : EntropySource class PlatformEntropySource : EntropySource
{ {
/** /**
* Returns: Minimum bytes required from the entropy source. * Returns: Minimum bytes required from the entropy source.
*/ */
override @property immutable(ubyte) threshold() const @safe pure nothrow override @property immutable(ubyte) threshold() const @safe pure nothrow
{ {
return 32; return 32;
} }
/** /**
* Returns: Whether this entropy source is strong. * Returns: Whether this entropy source is strong.
*/ */
override @property immutable(bool) strong() const @safe pure nothrow override @property immutable(bool) strong() const @safe pure nothrow
{ {
return true; return true;
} }
/** /**
* Poll the entropy source. * Poll the entropy source.
* *
* Params: * Params:
* output = Buffer to save the generate random sequence (the method will * output = Buffer to save the generate random sequence (the method will
* to fill the buffer). * to fill the buffer).
* *
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error. * or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/ */
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
out (length) out (length)
{ {
assert(length <= maxGather); assert(length <= maxGather);
} }
body body
{ {
// int getrandom(void *buf, size_t buflen, unsigned int flags); // int getrandom(void *buf, size_t buflen, unsigned int flags);
auto length = syscall(318, output.ptr, output.length, 0); auto length = syscall(318, output.ptr, output.length, 0);
Nullable!ubyte ret; Nullable!ubyte ret;
if (length >= 0) if (length >= 0)
{ {
ret = cast(ubyte) length; ret = cast(ubyte) length;
} }
return ret; return ret;
} }
} }
} }
/** /**
@ -156,165 +156,165 @@ version (linux)
* *
* output = entropy.random; * output = entropy.random;
* *
* defaultAllocator.finalize(entropy); * defaultAllocator.dispose(entropy);
* --- * ---
*/ */
class Entropy class Entropy
{ {
/// Entropy sources. /// Entropy sources.
protected EntropySource[] sources; protected EntropySource[] sources;
private ubyte sourceCount_; private ubyte sourceCount_;
private shared Allocator allocator; private shared Allocator allocator;
/// Entropy accumulator. /// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator; protected SHA!(maxGather * 8, 512) accumulator;
/** /**
* Params: * Params:
* maxSources = Maximum amount of entropy sources can be set. * maxSources = Maximum amount of entropy sources can be set.
* allocator = Allocator to allocate entropy sources available on the * allocator = Allocator to allocate entropy sources available on the
* system. * system.
*/ */
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator) this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
in in
{ {
assert(maxSources > 0 && maxSources <= ubyte.max); assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null); assert(allocator !is null);
} }
body body
{ {
allocator.resizeArray(sources, maxSources); allocator.resize(sources, maxSources);
version (linux) version (linux)
{ {
this ~= allocator.make!PlatformEntropySource; this ~= allocator.make!PlatformEntropySource;
} }
} }
/** /**
* Returns: Amount of the registered entropy sources. * Returns: Amount of the registered entropy sources.
*/ */
@property ubyte sourceCount() const @safe pure nothrow @property ubyte sourceCount() const @safe pure nothrow
{ {
return sourceCount_; return sourceCount_;
} }
/** /**
* Add an entropy source. * Add an entropy source.
* *
* Params: * Params:
* source = Entropy source. * source = Entropy source.
* *
* Returns: $(D_PSYMBOL this). * Returns: $(D_PSYMBOL this).
* *
* See_Also: * See_Also:
* $(D_PSYMBOL EntropySource) * $(D_PSYMBOL EntropySource)
*/ */
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
if (Op == "~") if (Op == "~")
in in
{ {
assert(sourceCount_ <= sources.length); assert(sourceCount_ <= sources.length);
} }
body body
{ {
sources[sourceCount_++] = source; sources[sourceCount_++] = source;
return this; return this;
} }
/** /**
* Returns: Generated random sequence. * Returns: Generated random sequence.
* *
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was * Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed. * registered or it failed.
*/ */
@property ubyte[blockSize] random() @property ubyte[blockSize] random()
in in
{ {
assert(sourceCount_ > 0, "No entropy sources defined."); assert(sourceCount_ > 0, "No entropy sources defined.");
} }
body body
{ {
bool haveStrong; bool haveStrong;
ushort done; ushort done;
ubyte[blockSize] output; ubyte[blockSize] output;
do do
{ {
ubyte[maxGather] buffer; ubyte[maxGather] buffer;
// Run through our entropy sources // Run through our entropy sources
for (ubyte i; i < sourceCount; ++i) for (ubyte i; i < sourceCount; ++i)
{ {
auto outputLength = sources[i].poll(buffer); auto outputLength = sources[i].poll(buffer);
if (!outputLength.isNull) if (!outputLength.isNull)
{ {
if (outputLength > 0) if (outputLength > 0)
{ {
update(i, buffer, outputLength); update(i, buffer, outputLength);
sources[i].size = cast(ushort) (sources[i].size + outputLength); sources[i].size = cast(ushort) (sources[i].size + outputLength);
} }
if (sources[i].size < sources[i].threshold) if (sources[i].size < sources[i].threshold)
{ {
continue; continue;
} }
else if (sources[i].strong) else if (sources[i].strong)
{ {
haveStrong = true; haveStrong = true;
} }
} }
done = 257; done = 257;
} }
} }
while (++done < 256); while (++done < 256);
if (!haveStrong) if (!haveStrong)
{ {
throw allocator.make!EntropyException("No strong entropy source defined."); throw allocator.make!EntropyException("No strong entropy source defined.");
} }
output = accumulator.finish(); output = accumulator.finish();
// Reset accumulator and counters and recycle existing entropy // Reset accumulator and counters and recycle existing entropy
accumulator.start(); accumulator.start();
// Perform second SHA-512 on entropy // Perform second SHA-512 on entropy
output = sha512Of(output); output = sha512Of(output);
for (ubyte i = 0; i < sourceCount; ++i) for (ubyte i = 0; i < sourceCount; ++i)
{ {
sources[i].size = 0; sources[i].size = 0;
} }
return output; return output;
} }
/** /**
* Update entropy accumulator. * Update entropy accumulator.
* *
* Params: * Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources). * sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source. * data = Data got from the entropy source.
* length = Length of the received data. * length = Length of the received data.
*/ */
protected void update(in ubyte sourceId, protected void update(in ubyte sourceId,
ref ubyte[maxGather] data, ref ubyte[maxGather] data,
ubyte length) @safe pure nothrow ubyte length) @safe pure nothrow
{ {
ubyte[2] header; ubyte[2] header;
if (length > blockSize) if (length > blockSize)
{ {
data[0..64] = sha512Of(data); data[0..64] = sha512Of(data);
length = blockSize; length = blockSize;
} }
header[0] = sourceId; header[0] = sourceId;
header[1] = length; header[1] = length;
accumulator.put(header); accumulator.put(header);
accumulator.put(data[0..length]); accumulator.put(data[0..length]);
} }
} }

View File

@ -15,53 +15,53 @@ module tanya.memory.allocator;
*/ */
interface Allocator interface Allocator
{ {
/** /**
* Returns: Alignment offered. * Returns: Alignment offered.
*/ */
@property uint alignment() const shared pure nothrow @safe @nogc; @property uint alignment() const shared pure nothrow @safe @nogc;
/** /**
* Allocates $(D_PARAM size) bytes of memory. * Allocates $(D_PARAM size) bytes of memory.
* *
* Params: * Params:
* size = Amount of memory to allocate. * size = Amount of memory to allocate.
* *
* Returns: Pointer to the new allocated memory. * Returns: Pointer to the new allocated memory.
*/ */
void[] allocate(in size_t size) shared nothrow @nogc; void[] allocate(in size_t size) shared nothrow @nogc;
/** /**
* Deallocates a memory block. * Deallocates a memory block.
* *
* Params: * Params:
* p = A pointer to the memory block to be freed. * p = A pointer to the memory block to be freed.
* *
* Returns: Whether the deallocation was successful. * Returns: Whether the deallocation was successful.
*/ */
bool deallocate(void[] p) shared nothrow @nogc; bool deallocate(void[] p) shared nothrow @nogc;
/** /**
* Increases or decreases the size of a memory block. * Increases or decreases the size of a memory block.
* *
* Params: * Params:
* p = A pointer to the memory block. * p = A pointer to the memory block.
* size = Size of the reallocated block. * size = Size of the reallocated block.
* *
* Returns: Pointer to the allocated memory. * Returns: Pointer to the allocated memory.
*/ */
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc; bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
/** /**
* Reallocates a memory block in place if possible or returns * Reallocates a memory block in place if possible or returns
* $(D_KEYWORD false). This function cannot be used to allocate or * $(D_KEYWORD false). This function cannot be used to allocate or
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false). * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
* *
* Params: * Params:
* p = A pointer to the memory block. * p = A pointer to the memory block.
* size = Size of the reallocated block. * size = Size of the reallocated block.
* *
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/ */
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc; bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
} }

View File

@ -0,0 +1,185 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.memory.mallocator;
import core.stdc.stdlib;
import std.algorithm.comparison;
import tanya.memory.allocator;
/**
* Wrapper for malloc/realloc/free from the C standard library.
*/
final class Mallocator : Allocator
{
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(in size_t size) shared nothrow @nogc
{
if (!size)
{
return null;
}
auto p = malloc(size + psize);
return p is null ? null : p[psize .. psize + size];
}
///
@nogc nothrow unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
Mallocator.instance.deallocate(p);
}
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared nothrow @nogc
{
if (p !is null)
{
free(p.ptr - psize);
}
return true;
}
///
@nogc nothrow unittest
{
void[] p;
assert(Mallocator.instance.deallocate(p));
p = Mallocator.instance.allocate(10);
assert(Mallocator.instance.deallocate(p));
}
/**
* Reallocating in place isn't supported.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD false).
*/
bool reallocateInPlace(ref void[] p, const size_t size) shared nothrow @nogc
{
return false;
}
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, const size_t size) shared nothrow @nogc
{
if (size == 0)
{
if (deallocate(p))
{
p = null;
return true;
}
}
else if (p is null)
{
p = allocate(size);
return p is null ? false : true;
}
else
{
auto r = realloc(p.ptr - psize, size + psize);
if (r !is null)
{
p = r[psize .. psize + size];
return true;
}
}
return false;
}
///
@nogc nothrow unittest
{
void[] p;
assert(Mallocator.instance.reallocate(p, 20));
assert(p.length == 20);
assert(Mallocator.instance.reallocate(p, 30));
assert(p.length == 30);
assert(Mallocator.instance.reallocate(p, 10));
assert(p.length == 10);
assert(Mallocator.instance.reallocate(p, 0));
assert(p is null);
}
/**
* Returns: The alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
{
return cast(uint) max(double.alignof, real.alignof);
}
/**
* Static allocator instance and initializer.
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property ref shared(Mallocator) instance() @nogc nothrow
{
if (instance_ is null)
{
immutable size = __traits(classInstanceSize, Mallocator) + psize;
void* p = malloc(size);
if (p !is null)
{
p[psize .. size] = typeid(Mallocator).initializer[];
instance_ = cast(shared Mallocator) p[psize .. size].ptr;
}
}
return instance_;
}
///
@nogc nothrow unittest
{
assert(instance is instance);
}
private enum psize = 8;
private shared static Mallocator instance_;
}

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,8 @@
module tanya.memory; module tanya.memory;
import core.exception; import core.exception;
public import std.experimental.allocator : make, makeArray; import std.algorithm.iteration;
public import std.experimental.allocator : make;
import std.traits; import std.traits;
public import tanya.memory.allocator; public import tanya.memory.allocator;
@ -23,59 +24,61 @@ public import tanya.memory.allocator;
*/ */
mixin template DefaultAllocator() mixin template DefaultAllocator()
{ {
/// Allocator. /// Allocator.
protected shared Allocator allocator_; protected shared Allocator allocator_;
/** /**
* Params: * Params:
* allocator = The allocator should be used. * allocator = The allocator should be used.
*/ *
this(shared Allocator allocator) * Precondition: $(D_INLINECODE allocator_ !is null)
in */
{ this(shared Allocator allocator)
assert(allocator !is null); in
} {
body assert(allocator !is null);
{ }
this.allocator_ = allocator; body
} {
this.allocator_ = allocator;
}
/** /**
* This property checks if the allocator was set in the constructor * This property checks if the allocator was set in the constructor
* and sets it to the default one, if not. * and sets it to the default one, if not.
* *
* Returns: Used allocator. * Returns: Used allocator.
* *
* Postcondition: $(D_INLINECODE allocator_ !is null) * Postcondition: $(D_INLINECODE allocator !is null)
*/ */
protected @property shared(Allocator) allocator() nothrow @safe @nogc protected @property shared(Allocator) allocator() nothrow @safe @nogc
out (allocator) out (allocator)
{ {
assert(allocator !is null); assert(allocator !is null);
} }
body body
{ {
if (allocator_ is null) if (allocator_ is null)
{ {
allocator_ = defaultAllocator; allocator_ = defaultAllocator;
} }
return allocator_; return allocator_;
} }
/// Ditto. /// Ditto.
@property shared(Allocator) allocator() const nothrow @trusted @nogc @property shared(Allocator) allocator() const nothrow @trusted @nogc
out (allocator) out (allocator)
{ {
assert(allocator !is null); assert(allocator !is null);
} }
body body
{ {
if (allocator_ is null) if (allocator_ is null)
{ {
return defaultAllocator; return defaultAllocator;
} }
return cast(shared Allocator) allocator_; return cast(shared Allocator) allocator_;
} }
} }
// From druntime // From druntime
@ -85,28 +88,28 @@ shared Allocator allocator;
shared static this() nothrow @trusted @nogc shared static this() nothrow @trusted @nogc
{ {
import tanya.memory.mmappool; import tanya.memory.mmappool;
allocator = MmapPool.instance; allocator = MmapPool.instance;
} }
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc @property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
out (allocator) out (allocator)
{ {
assert(allocator !is null); assert(allocator !is null);
} }
body body
{ {
return allocator; return allocator;
} }
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc @property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
in in
{ {
assert(allocator !is null); assert(allocator !is null);
} }
body body
{ {
.allocator = allocator; .allocator = allocator;
} }
/** /**
@ -114,101 +117,166 @@ body
* object of type $(D_PARAM T). * object of type $(D_PARAM T).
* *
* Params: * Params:
* T = Object type. * T = Object type.
*/ */
template stateSize(T) template stateSize(T)
{ {
static if (is(T == class) || is(T == interface)) static if (is(T == class) || is(T == interface))
{ {
enum stateSize = __traits(classInstanceSize, T); enum stateSize = __traits(classInstanceSize, T);
} }
else else
{ {
enum stateSize = T.sizeof; enum stateSize = T.sizeof;
} }
} }
/** /**
* Params: * Params:
* size = Raw size. * size = Raw size.
* alignment = Alignment. * alignment = Alignment.
* *
* Returns: Aligned size. * Returns: Aligned size.
*/ */
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc size_t alignedSize(const size_t size, const size_t alignment = 8)
pure nothrow @safe @nogc
{ {
return (size - 1) / alignment * alignment + alignment; return (size - 1) / alignment * alignment + alignment;
} }
/** /**
* Internal function used to create, resize or destroy a dynamic array. It * Internal function used to create, resize or destroy a dynamic array. It
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new * may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array is initialized only if $(D_PARAM Init) * allocated part of the array isn't initialized. This function can be trusted
* is set. This function can be trusted only in the data structures that * only in the data structures that can ensure that the array is
* can ensure that the array is allocated/rellocated/deallocated with the * allocated/rellocated/deallocated with the same allocator.
* same allocator.
* *
* Params: * Params:
* T = Element type of the array being created. * T = Element type of the array being created.
* Init = If should be initialized. * allocator = The allocator used for getting memory.
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn. * array = A reference to the array being changed.
* allocator = The allocator used for getting memory. * length = New array length.
* array = A reference to the array being changed.
* length = New array length.
* *
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could * Returns: $(D_PARAM array).
* not be reallocated. In the latter
*/ */
package(tanya) bool resize(T, package(tanya) T[] resize(T)(shared Allocator allocator,
bool Init = true, auto ref T[] array,
bool Throws = true) const size_t length) @trusted
(shared Allocator allocator,
ref T[] array,
in size_t length) @trusted
{ {
void[] buf = array; if (length == 0)
static if (Init) {
{ if (allocator.deallocate(array))
immutable oldLength = array.length; {
} return null;
if (!allocator.reallocate(buf, length * T.sizeof)) }
{ else
static if (Throws) {
{ onOutOfMemoryErrorNoGC();
onOutOfMemoryError; }
} }
return false;
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
static if (Init) void[] buf = array;
{ if (!allocator.reallocate(buf, length * T.sizeof))
if (oldLength < length) {
{ onOutOfMemoryErrorNoGC();
array[oldLength .. $] = T.init; }
} // Casting from void[] is unsafe, but we know we cast to the original type.
} array = cast(T[]) buf;
return true;
return array;
} }
package(tanya) alias resizeArray = resize;
/// private unittest
unittest
{ {
int[] p; int[] p;
defaultAllocator.resizeArray(p, 20); p = defaultAllocator.resize(p, 20);
assert(p.length == 20); assert(p.length == 20);
defaultAllocator.resizeArray(p, 30); p = defaultAllocator.resize(p, 30);
assert(p.length == 30); assert(p.length == 30);
defaultAllocator.resizeArray(p, 10); p = defaultAllocator.resize(p, 10);
assert(p.length == 10); assert(p.length == 10);
defaultAllocator.resizeArray(p, 0); p = defaultAllocator.resize(p, 0);
assert(p is null); assert(p is null);
}
/*
* Destroys the object.
* Returns the memory should be freed.
*/
package(tanya) void[] finalize(T)(ref T* p)
{
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
return (cast(void*) p)[0 .. T.sizeof];
}
package(tanya) void[] finalize(T)(ref T p)
if (is(T == class) || is(T == interface))
{
if (p is null)
{
return null;
}
static if (is(T == interface))
{
version(Windows)
{
import core.sys.windows.unknwn : IUnknown;
static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in "
~ __PRETTY_FUNCTION__);
}
auto ob = cast(Object) p;
}
else
{
alias ob = p;
}
auto ptr = cast(void*) ob;
auto support = ptr[0 .. typeid(ob).initializer.length];
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return null;
}
auto pc = cast(ClassInfo*) *ppv;
scope (exit)
{
*ppv = null;
}
auto c = *pc;
do
{
// Assume the destructor is @nogc. Leave it nothrow since the destructor
// shouldn't throw and if it does, it is an error anyway.
if (c.destructor)
{
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
return support;
}
package(tanya) void[] finalize(T)(ref T[] p)
{
static if (hasElaborateDestructor!(typeof(p[0])))
{
p.each!((ref e) => destroy(e));
}
return p;
} }
/** /**
@ -217,101 +285,25 @@ unittest
* allocator. * allocator.
* *
* Params: * Params:
* T = Type of $(D_PARAM p). * T = Type of $(D_PARAM p).
* allocator = Allocator the $(D_PARAM p) was allocated with. * allocator = Allocator the $(D_PARAM p) was allocated with.
* p = Object or array to be destroyed. * p = Object or array to be destroyed.
*/ */
void dispose(T)(shared Allocator allocator, auto ref T* p)
{
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
p = null;
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T p) void dispose(T)(shared Allocator allocator, auto ref T p)
if (is(T == class) || is(T == interface))
{ {
if (p is null) () @trusted { allocator.deallocate(finalize(p)); }();
{ p = null;
return;
}
static if (is(T == interface))
{
version(Windows)
{
import core.sys.windows.unknwn : IUnknown;
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
~ __PRETTY_FUNCTION__);
}
auto ob = cast(Object) p;
}
else
{
alias ob = p;
}
auto ptr = cast(void *) ob;
auto support = ptr[0 .. typeid(ob).initializer.length];
scope (success)
{
() @trusted { allocator.deallocate(support); }();
p = null;
}
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return;
}
auto pc = cast(ClassInfo*) *ppv;
scope (exit)
{
*ppv = null;
}
auto c = *pc;
do
{
// Assume the destructor is @nogc. Leave it nothrow since the destructor
// shouldn't throw and if it does, it is an error anyway.
if (c.destructor)
{
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
} }
/// Ditto. private unittest
void dispose(T)(shared Allocator allocator, auto ref T[] p)
{ {
static if (hasElaborateDestructor!(typeof(p[0]))) struct S
{ {
import std.algorithm.iteration; ~this()
p.each!(e => destroy(e)); {
} }
() @trusted { allocator.deallocate(p); }(); }
p = null; auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
}
unittest defaultAllocator.dispose(p);
{
struct S
{
~this()
{
}
}
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
defaultAllocator.dispose(p);
} }

File diff suppressed because it is too large Load Diff

356
source/tanya/network/inet.d Normal file
View File

@ -0,0 +1,356 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Internet utilities.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.network.inet;
import std.math;
import std.range.primitives;
import std.traits;
version (unittest)
{
version (Windows)
{
import core.sys.windows.winsock2;
version = PlattformUnittest;
}
else version (Posix)
{
import core.sys.posix.arpa.inet;
version = PlattformUnittest;
}
}
/**
* Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
*
* The range is bidirectional. The byte order is always big-endian.
*
* It can accept any unsigned integral type but the value should fit
* in $(D_PARAM L) bytes.
*
* Params:
* L = Desired range length.
*/
struct NetworkOrder(uint L)
if (L > ubyte.sizeof && L <= ulong.sizeof)
{
static if (L > uint.sizeof)
{
private alias StorageType = ulong;
}
else static if (L > ushort.sizeof)
{
private alias StorageType = uint;
}
else static if (L > ubyte.sizeof)
{
private alias StorageType = ushort;
}
else
{
private alias StorageType = ubyte;
}
private StorageType value;
private size_t size = L;
const pure nothrow @safe @nogc invariant
{
assert(this.size <= L);
}
/**
* Constructs a new range.
*
* $(D_PARAM T) can be any unsigned type but $(D_PARAM value) cannot be
* larger than the maximum can be stored in $(D_PARAM L) bytes. Otherwise
* an assertion failure will be caused.
*
* Params:
* T = Value type.
* value = The value should be represented by this range.
*
* Precondition: $(D_INLINECODE value <= 2 ^^ (length * 8) - 1).
*/
this(T)(const T value)
if (isUnsigned!T)
in
{
assert(value <= pow(2, L * 8) - 1);
}
body
{
this.value = value & StorageType.max;
}
/**
* Returns: LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte back() const
in
{
assert(this.length > 0);
}
body
{
return this.value & 0xff;
}
/**
* Returns: MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte front() const
in
{
assert(this.length > 0);
}
body
{
return (this.value >> ((this.length - 1) * 8)) & 0xff;
}
/**
* Eliminates the LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popBack()
in
{
assert(this.length > 0);
}
body
{
this.value >>= 8;
--this.size;
}
/**
* Eliminates the MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popFront()
in
{
assert(this.length > 0);
}
body
{
this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
--this.size;
}
/**
* Returns: Copy of this range.
*/
typeof(this) save() const
{
return this;
}
/**
* Returns: Whether the range is empty.
*/
@property bool empty() const
{
return this.length == 0;
}
/**
* Returns: Byte length.
*/
@property size_t length() const
{
return this.size;
}
}
///
pure nothrow @safe @nogc unittest
{
auto networkOrder = NetworkOrder!3(0xae34e2u);
assert(!networkOrder.empty);
assert(networkOrder.front == 0xae);
networkOrder.popFront();
assert(networkOrder.length == 2);
assert(networkOrder.front == 0x34);
assert(networkOrder.back == 0xe2);
networkOrder.popBack();
assert(networkOrder.length == 1);
assert(networkOrder.front == 0x34);
assert(networkOrder.front == 0x34);
networkOrder.popFront();
assert(networkOrder.empty);
}
// Static.
private unittest
{
static assert(isBidirectionalRange!(NetworkOrder!4));
static assert(isBidirectionalRange!(NetworkOrder!8));
static assert(!is(NetworkOrder!9));
static assert(!is(NetworkOrder!1));
}
// Tests against the system's htonl, htons.
version (PlattformUnittest)
{
private unittest
{
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
{
const value = pow(2, counter) - 1;
const inNetworkOrder = htonl(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.length == 4);
assert(!networkOrder.empty);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 3));
networkOrder.popBack();
assert(networkOrder.length == 3);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 2));
networkOrder.popFront();
assert(networkOrder.length == 2);
assert(networkOrder.front == *(p + 1));
assert(networkOrder.back == *(p + 2));
networkOrder.popFront();
assert(networkOrder.length == 1);
assert(networkOrder.front == *(p + 2));
assert(networkOrder.back == *(p + 2));
networkOrder.popBack();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
}
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
{
const value = cast(ushort) (pow(2, counter) - 1);
const inNetworkOrder = htons(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!2(value);
assert(networkOrder.length == 2);
assert(!networkOrder.empty);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 1));
networkOrder.popBack();
assert(networkOrder.length == 1);
assert(networkOrder.front == *p);
assert(networkOrder.back == *p);
networkOrder.popBack();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
networkOrder = NetworkOrder!2(value);
networkOrder.popFront();
assert(networkOrder.length == 1);
assert(networkOrder.front == *(p + 1));
assert(networkOrder.back == *(p + 1));
networkOrder.popFront();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
}
}
}
/**
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
* $(D_PARAM T).
*
* The byte order of $(D_PARAM r) is assumed to be big-endian. The length
* cannot be larger than $(D_INLINECODE T.sizeof). Otherwise an assertion
* failure will be caused.
*
* Params:
* T = Desired return type.
* R = Range type.
* range = Input range.
*
* Returns: Integral representation of $(D_PARAM range) with the host byte
* order.
*/
T toHostOrder(T = size_t, R)(R range)
if (isInputRange!R
&& !isInfinite!R
&& is(Unqual!(ElementType!R) == ubyte)
&& isUnsigned!T)
{
T ret;
ushort pos = T.sizeof * 8;
for (; !range.empty && range.front == 0; pos -= 8, range.popFront())
{
}
for (; !range.empty; range.popFront())
{
assert(pos != 0);
pos -= 8;
ret |= (cast(T) range.front) << pos;
}
return ret >> pos;
}
///
pure nothrow @safe @nogc unittest
{
const value = 0xae34e2u;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.toHostOrder() == value);
}
// Tests against the system's htonl, htons.
version (PlattformUnittest)
{
private unittest
{
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
{
const value = pow(2, counter) - 1;
const inNetworkOrder = htonl(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!4(value);
assert(p[0 .. uint.sizeof].toHostOrder() == value);
}
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
{
const value = cast(ushort) (pow(2, counter) - 1);
const inNetworkOrder = htons(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!2(value);
assert(p[0 .. ushort.sizeof].toHostOrder() == value);
}
}
}

View File

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Network programming.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.network;
public import tanya.network.inet;
public import tanya.network.socket;
public import tanya.network.url;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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