diff --git a/source/tanya/async/event/epoll.d b/source/tanya/async/event/epoll.d index c10b241..5809dd4 100644 --- a/source/tanya/async/event/epoll.d +++ b/source/tanya/async/event/epoll.d @@ -18,7 +18,7 @@ import tanya.async.event.selector; import tanya.async.loop; import tanya.async.transport; import tanya.async.watcher; -import tanya.container.vector; +import tanya.container.array; import tanya.memory; import tanya.memory.mmappool; import tanya.network.socket; @@ -29,153 +29,153 @@ import std.algorithm.comparison; extern (C) nothrow @nogc { - int epoll_create1(int flags); - int epoll_ctl (int epfd, int op, int fd, epoll_event *event); - int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout); + int epoll_create1(int flags); + int epoll_ctl (int epfd, int op, int fd, epoll_event *event); + int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout); } final class EpollLoop : SelectorLoop { - protected int fd; - private Vector!epoll_event events; + protected int fd; + private Array!epoll_event events; - /** - * Initializes the loop. - */ - this() @nogc - { - if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0) - { - throw defaultAllocator.make!BadLoopException("epoll initialization failed"); - } - super(); - events = Vector!epoll_event(maxEvents, MmapPool.instance); - } + /** + * Initializes the loop. + */ + this() @nogc + { + if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + { + throw defaultAllocator.make!BadLoopException("epoll initialization failed"); + } + super(); + events = Array!epoll_event(maxEvents, MmapPool.instance); + } - /** - * Frees loop internals. - */ - ~this() @nogc - { - close(fd); - } + /** + * Frees loop internals. + */ + ~this() @nogc + { + close(fd); + } - /** - * Should be called if the backend configuration changes. - * - * Params: - * watcher = Watcher. - * oldEvents = The events were already set. - * events = The events should be set. - * - * Returns: $(D_KEYWORD true) if the operation was successful. - */ - protected override bool reify(SocketWatcher watcher, - EventMask oldEvents, - EventMask events) @nogc - { - int op = EPOLL_CTL_DEL; - epoll_event ev; + /** + * Should be called if the backend configuration changes. + * + * Params: + * watcher = Watcher. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + protected override bool reify(SocketWatcher watcher, + EventMask oldEvents, + EventMask events) @nogc + { + int op = EPOLL_CTL_DEL; + epoll_event ev; - if (events == oldEvents) - { - return true; - } - if (events && oldEvents) - { - op = EPOLL_CTL_MOD; - } - else if (events && !oldEvents) - { - op = EPOLL_CTL_ADD; - } + if (events == oldEvents) + { + return true; + } + if (events && oldEvents) + { + op = EPOLL_CTL_MOD; + } + else if (events && !oldEvents) + { + op = EPOLL_CTL_ADD; + } - ev.data.fd = watcher.socket.handle; - ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0) - | (events & Event.write ? EPOLLOUT : 0) - | EPOLLET; + ev.data.fd = watcher.socket.handle; + ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0) + | (events & Event.write ? EPOLLOUT : 0) + | 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. - */ - protected override void poll() @nogc - { - // Don't block - immutable timeout = cast(immutable int) blockTime.total!"msecs"; - auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout); + /** + * Does the actual polling. + */ + protected override void poll() @nogc + { + // Don't block + immutable timeout = cast(immutable int) blockTime.total!"msecs"; + auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout); - if (eventCount < 0) - { - if (errno != EINTR) - { - throw defaultAllocator.make!BadLoopException(); - } - return; - } + if (eventCount < 0) + { + if (errno != EINTR) + { + throw defaultAllocator.make!BadLoopException(); + } + return; + } - for (auto i = 0; i < eventCount; ++i) - { - auto transport = cast(StreamTransport) connections[events[i].data.fd]; + for (auto i = 0; i < eventCount; ++i) + { + auto transport = cast(StreamTransport) connections[events[i].data.fd]; - if (transport is null) - { - auto connection = cast(ConnectionWatcher) connections[events[i].data.fd]; - assert(connection !is null); + if (transport is null) + { + auto connection = cast(ConnectionWatcher) connections[events[i].data.fd]; + assert(connection !is null); - acceptConnections(connection); - } - else if (events[i].events & EPOLLERR) - { - kill(transport); - continue; - } - else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP)) - { - SocketException exception; - try - { - ptrdiff_t received; - do - { - received = transport.socket.receive(transport.output[]); - transport.output += received; - } - while (received); - } - catch (SocketException e) - { - exception = e; - } - if (transport.socket.disconnected) - { - kill(transport, exception); - continue; - } - else if (transport.output.length) - { - pendings.enqueue(transport); - } - } - if (events[i].events & EPOLLOUT) - { - transport.writeReady = true; - if (transport.input.length) - { - feed(transport); - } - } - } - } + acceptConnections(connection); + } + else if (events[i].events & EPOLLERR) + { + kill(transport); + continue; + } + else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP)) + { + SocketException exception; + try + { + ptrdiff_t received; + do + { + received = transport.socket.receive(transport.output[]); + transport.output += received; + } + while (received); + } + catch (SocketException e) + { + exception = e; + } + if (transport.socket.disconnected) + { + kill(transport, exception); + continue; + } + else if (transport.output.length) + { + pendings.enqueue(transport); + } + } + if (events[i].events & EPOLLOUT) + { + transport.writeReady = true; + if (transport.input.length) + { + feed(transport); + } + } + } + } - /** - * Returns: The blocking time. - */ - override protected @property inout(Duration) blockTime() - inout @safe pure nothrow - { - return min(super.blockTime, 1.dur!"seconds"); - } + /** + * Returns: The blocking time. + */ + override protected @property inout(Duration) blockTime() + inout @safe pure nothrow + { + return min(super.blockTime, 1.dur!"seconds"); + } } diff --git a/source/tanya/async/event/iocp.d b/source/tanya/async/event/iocp.d index 26678e1..fe05cc3 100644 --- a/source/tanya/async/event/iocp.d +++ b/source/tanya/async/event/iocp.d @@ -31,354 +31,354 @@ import core.sys.windows.winsock2; */ 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. - * - * Params: - * socket = Socket. - * - * Precondition: $(D_INLINECODE socket !is null) - */ - this(OverlappedConnectedSocket socket) @nogc - { - super(socket); - output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); - input = WriteBuffer!ubyte(8192, MmapPool.instance); - active = true; - } + /** + * Creates new completion port transport. + * + * Params: + * socket = Socket. + * + * Precondition: $(D_INLINECODE socket !is null) + */ + this(OverlappedConnectedSocket socket) @nogc + { + super(socket); + output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); + input = WriteBuffer!ubyte(8192, MmapPool.instance); + active = true; + } - /** - * Returns: Socket. - * - * Postcondition: $(D_INLINECODE socket !is null) - */ - override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc - out (socket) - { - assert(socket !is null); - } - body - { - return cast(OverlappedConnectedSocket) socket_; - } + /** + * Returns: Socket. + * + * Postcondition: $(D_INLINECODE socket !is null) + */ + override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc + out (socket) + { + assert(socket !is null); + } + body + { + return cast(OverlappedConnectedSocket) socket_; + } - /** - * Returns $(D_PARAM true) if the transport is closing or closed. - */ - bool isClosing() const pure nothrow @safe @nogc - { - return closing; - } + /** + * Returns $(D_PARAM true) if the transport is closing or closed. + */ + bool isClosing() const pure nothrow @safe @nogc + { + return closing; + } - /** - * Close the transport. - * - * Buffered data will be flushed. No more data will be received. - */ - void close() pure nothrow @safe @nogc - { - closing = true; - } + /** + * Close the transport. + * + * Buffered data will be flushed. No more data will be received. + */ + void close() pure nothrow @safe @nogc + { + closing = true; + } - /** - * Write some data to the transport. - * - * Params: - * data = Data to send. - */ - void write(ubyte[] data) @nogc - { - input ~= data; - } + /** + * Write some data to the transport. + * + * Params: + * data = Data to send. + */ + void write(ubyte[] data) @nogc + { + input ~= data; + } - /** - * Returns: Application protocol. - */ - @property Protocol protocol() pure nothrow @safe @nogc - { - return protocol_; - } + /** + * Returns: Application protocol. + */ + @property Protocol protocol() pure nothrow @safe @nogc + { + return protocol_; + } - /** - * Switches the protocol. - * - * The protocol is deallocated by the event loop, it should currently be - * allocated with $(D_PSYMBOL MmapPool). - * - * Params: - * protocol = Application protocol. - * - * Precondition: $(D_INLINECODE protocol !is null) - */ - @property void protocol(Protocol protocol) pure nothrow @safe @nogc - in - { - assert(protocol !is null); - } - body - { - protocol_ = protocol; - } + /** + * Switches the protocol. + * + * The protocol is deallocated by the event loop, it should currently be + * allocated with $(D_PSYMBOL MmapPool). + * + * Params: + * protocol = Application protocol. + * + * Precondition: $(D_INLINECODE protocol !is null) + */ + @property void protocol(Protocol protocol) pure nothrow @safe @nogc + in + { + assert(protocol !is null); + } + body + { + protocol_ = protocol; + } - /** - * Invokes the watcher callback. - */ - override void invoke() @nogc - { - if (output.length) - { - immutable empty = input.length == 0; - protocol.received(output[0 .. $]); - output.clear(); - if (empty) - { - SocketState overlapped; - try - { - overlapped = MmapPool.instance.make!SocketState; - socket.beginSend(input[], overlapped); - } - catch (SocketException e) - { - MmapPool.instance.dispose(overlapped); - MmapPool.instance.dispose(e); - } - } - } - else - { - protocol.disconnected(exception); - MmapPool.instance.dispose(protocol_); - defaultAllocator.dispose(exception); - active = false; - } - } + /** + * Invokes the watcher callback. + */ + override void invoke() @nogc + { + if (output.length) + { + immutable empty = input.length == 0; + protocol.received(output[0 .. $]); + output.clear(); + if (empty) + { + SocketState overlapped; + try + { + overlapped = MmapPool.instance.make!SocketState; + socket.beginSend(input[], overlapped); + } + catch (SocketException e) + { + MmapPool.instance.dispose(overlapped); + MmapPool.instance.dispose(e); + } + } + } + else + { + protocol.disconnected(exception); + MmapPool.instance.dispose(protocol_); + defaultAllocator.dispose(exception); + active = false; + } + } } final class IOCPLoop : Loop { - protected HANDLE completionPort; + protected HANDLE completionPort; - protected OVERLAPPED overlap; + protected OVERLAPPED overlap; - /** - * Initializes the loop. - */ - this() @nogc - { - super(); + /** + * Initializes the loop. + */ + this() @nogc + { + super(); - completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); - if (!completionPort) - { - throw make!BadLoopException(defaultAllocator, - "Creating completion port failed"); - } - } + completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (!completionPort) + { + throw make!BadLoopException(defaultAllocator, + "Creating completion port failed"); + } + } - /** - * Should be called if the backend configuration changes. - * - * Params: - * watcher = Watcher. - * oldEvents = The events were already set. - * events = The events should be set. - * - * Returns: $(D_KEYWORD true) if the operation was successful. - */ - override protected bool reify(SocketWatcher watcher, - EventMask oldEvents, - EventMask events) @nogc - { - SocketState overlapped; - if (!(oldEvents & Event.accept) && (events & Event.accept)) - { - auto socket = cast(OverlappedStreamSocket) watcher.socket; - assert(socket !is null); + /** + * Should be called if the backend configuration changes. + * + * Params: + * watcher = Watcher. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + override protected bool reify(SocketWatcher watcher, + EventMask oldEvents, + EventMask events) @nogc + { + SocketState overlapped; + if (!(oldEvents & Event.accept) && (events & Event.accept)) + { + auto socket = cast(OverlappedStreamSocket) watcher.socket; + assert(socket !is null); - if (CreateIoCompletionPort(cast(HANDLE) socket.handle, - completionPort, - cast(ULONG_PTR) (cast(void*) watcher), - 0) !is completionPort) - { - return false; - } + if (CreateIoCompletionPort(cast(HANDLE) socket.handle, + completionPort, + cast(ULONG_PTR) (cast(void*) watcher), + 0) !is completionPort) + { + return false; + } - try - { - overlapped = MmapPool.instance.make!SocketState; - socket.beginAccept(overlapped); - } - catch (SocketException e) - { - MmapPool.instance.dispose(overlapped); - defaultAllocator.dispose(e); - return false; - } - } - if (!(oldEvents & Event.read) && (events & Event.read) - || !(oldEvents & Event.write) && (events & Event.write)) - { - auto transport = cast(StreamTransport) watcher; - assert(transport !is null); + try + { + overlapped = MmapPool.instance.make!SocketState; + socket.beginAccept(overlapped); + } + catch (SocketException e) + { + MmapPool.instance.dispose(overlapped); + defaultAllocator.dispose(e); + return false; + } + } + if (!(oldEvents & Event.read) && (events & Event.read) + || !(oldEvents & Event.write) && (events & Event.write)) + { + auto transport = cast(StreamTransport) watcher; + assert(transport !is null); - if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle, - completionPort, - cast(ULONG_PTR) (cast(void*) watcher), - 0) !is completionPort) - { - return false; - } + if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle, + completionPort, + cast(ULONG_PTR) (cast(void*) watcher), + 0) !is completionPort) + { + return false; + } - // Begin to read - if (!(oldEvents & Event.read) && (events & Event.read)) - { - try - { - overlapped = MmapPool.instance.make!SocketState; - transport.socket.beginReceive(transport.output[], overlapped); - } - catch (SocketException e) - { - MmapPool.instance.dispose(overlapped); - defaultAllocator.dispose(e); - return false; - } - } - } - return true; - } + // Begin to read + if (!(oldEvents & Event.read) && (events & Event.read)) + { + try + { + overlapped = MmapPool.instance.make!SocketState; + transport.socket.beginReceive(transport.output[], overlapped); + } + catch (SocketException e) + { + MmapPool.instance.dispose(overlapped); + defaultAllocator.dispose(e); + return false; + } + } + } + return true; + } - private void kill(StreamTransport transport, - SocketException exception = null) @nogc - in - { - assert(transport !is null); - } - body - { - transport.socket.shutdown(); - defaultAllocator.dispose(transport.socket); - transport.exception = exception; - pendings.enqueue(transport); - } + private void kill(StreamTransport transport, + SocketException exception = null) @nogc + in + { + assert(transport !is null); + } + body + { + transport.socket.shutdown(); + defaultAllocator.dispose(transport.socket); + transport.exception = exception; + pendings.enqueue(transport); + } - /** - * Does the actual polling. - */ - override protected void poll() @nogc - { - DWORD lpNumberOfBytes; - ULONG_PTR key; - LPOVERLAPPED overlap; - immutable timeout = cast(immutable int) blockTime.total!"msecs"; + /** + * Does the actual polling. + */ + override protected void poll() @nogc + { + DWORD lpNumberOfBytes; + ULONG_PTR key; + LPOVERLAPPED overlap; + immutable timeout = cast(immutable int) blockTime.total!"msecs"; - auto result = GetQueuedCompletionStatus(completionPort, - &lpNumberOfBytes, - &key, - &overlap, - timeout); - if (result == FALSE && overlap == NULL) - { - return; // Timeout - } + auto result = GetQueuedCompletionStatus(completionPort, + &lpNumberOfBytes, + &key, + &overlap, + timeout); + if (result == FALSE && overlap == NULL) + { + return; // Timeout + } - auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8)); - assert(overlapped !is null); - scope (failure) - { - MmapPool.instance.dispose(overlapped); - } + auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8)); + assert(overlapped !is null); + scope (failure) + { + MmapPool.instance.dispose(overlapped); + } - switch (overlapped.event) - { - case OverlappedSocketEvent.accept: - auto connection = cast(ConnectionWatcher) (cast(void*) key); - assert(connection !is null); + switch (overlapped.event) + { + case OverlappedSocketEvent.accept: + auto connection = cast(ConnectionWatcher) (cast(void*) key); + assert(connection !is null); - auto listener = cast(OverlappedStreamSocket) connection.socket; - assert(listener !is null); + auto listener = cast(OverlappedStreamSocket) connection.socket; + assert(listener !is null); - auto socket = listener.endAccept(overlapped); - auto transport = MmapPool.instance.make!StreamTransport(socket); + auto socket = listener.endAccept(overlapped); + 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); - listener.beginAccept(overlapped); - break; - case OverlappedSocketEvent.read: - auto transport = cast(StreamTransport) (cast(void*) key); - assert(transport !is null); + pendings.enqueue(connection); + listener.beginAccept(overlapped); + break; + case OverlappedSocketEvent.read: + auto transport = cast(StreamTransport) (cast(void*) key); + assert(transport !is null); - if (!transport.active) - { - MmapPool.instance.dispose(transport); - MmapPool.instance.dispose(overlapped); - return; - } + if (!transport.active) + { + MmapPool.instance.dispose(transport); + MmapPool.instance.dispose(overlapped); + return; + } - int received; - SocketException exception; - try - { - received = transport.socket.endReceive(overlapped); - } - catch (SocketException e) - { - exception = e; - } - if (transport.socket.disconnected) - { - // We want to get one last notification to destroy the watcher. - transport.socket.beginReceive(transport.output[], overlapped); - kill(transport, exception); - } - else if (received > 0) - { - immutable full = transport.output.free == received; + int received; + SocketException exception; + try + { + received = transport.socket.endReceive(overlapped); + } + catch (SocketException e) + { + exception = e; + } + if (transport.socket.disconnected) + { + // We want to get one last notification to destroy the watcher. + transport.socket.beginReceive(transport.output[], overlapped); + kill(transport, exception); + } + else if (received > 0) + { + immutable full = transport.output.free == received; - transport.output += received; - // Receive was interrupted because the buffer is full. We have to continue. - if (full) - { - transport.socket.beginReceive(transport.output[], overlapped); - } - pendings.enqueue(transport); - } - break; - case OverlappedSocketEvent.write: - auto transport = cast(StreamTransport) (cast(void*) key); - assert(transport !is null); + transport.output += received; + // Receive was interrupted because the buffer is full. We have to continue. + if (full) + { + transport.socket.beginReceive(transport.output[], overlapped); + } + pendings.enqueue(transport); + } + break; + case OverlappedSocketEvent.write: + auto transport = cast(StreamTransport) (cast(void*) key); + assert(transport !is null); - transport.input += transport.socket.endSend(overlapped); - if (transport.input.length > 0) - { - transport.socket.beginSend(transport.input[], overlapped); - } - else - { - transport.socket.beginReceive(transport.output[], overlapped); - if (transport.isClosing()) - { - kill(transport); - } - } - break; - default: - assert(false, "Unknown event"); - } - } -} \ No newline at end of file + transport.input += transport.socket.endSend(overlapped); + if (transport.input.length > 0) + { + transport.socket.beginSend(transport.input[], overlapped); + } + else + { + transport.socket.beginReceive(transport.output[], overlapped); + if (transport.isClosing()) + { + kill(transport); + } + } + break; + default: + assert(false, "Unknown event"); + } + } +} diff --git a/source/tanya/async/event/kqueue.d b/source/tanya/async/event/kqueue.d index 8673ff7..5cadec6 100644 --- a/source/tanya/async/event/kqueue.d +++ b/source/tanya/async/event/kqueue.d @@ -12,31 +12,31 @@ module tanya.async.event.kqueue; version (OSX) { - version = MacBSD; + version = MacBSD; } else version (iOS) { - version = MacBSD; + version = MacBSD; } else version (TVOS) { - version = MacBSD; + version = MacBSD; } else version (WatchOS) { - version = MacBSD; + version = MacBSD; } else version (FreeBSD) { - version = MacBSD; + version = MacBSD; } else version (OpenBSD) { - version = MacBSD; + version = MacBSD; } else version (DragonFlyBSD) { - version = MacBSD; + version = MacBSD; } version (MacBSD): @@ -50,62 +50,62 @@ import tanya.async.event.selector; import tanya.async.loop; import tanya.async.transport; import tanya.async.watcher; -import tanya.container.vector; +import tanya.container.array; import tanya.memory; import tanya.memory.mmappool; import tanya.network.socket; void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc { - *kevp = kevent_t(args); + *kevp = kevent_t(args); } enum : short { - EVFILT_READ = -1, - EVFILT_WRITE = -2, - EVFILT_AIO = -3, /* attached to aio requests */ - EVFILT_VNODE = -4, /* attached to vnodes */ - EVFILT_PROC = -5, /* attached to struct proc */ - EVFILT_SIGNAL = -6, /* attached to struct proc */ - EVFILT_TIMER = -7, /* timers */ - EVFILT_MACHPORT = -8, /* Mach portsets */ - EVFILT_FS = -9, /* filesystem events */ - EVFILT_USER = -10, /* User events */ - EVFILT_VM = -12, /* virtual memory events */ - EVFILT_SYSCOUNT = 11 + EVFILT_READ = -1, + EVFILT_WRITE = -2, + EVFILT_AIO = -3, /* attached to aio requests */ + EVFILT_VNODE = -4, /* attached to vnodes */ + EVFILT_PROC = -5, /* attached to struct proc */ + EVFILT_SIGNAL = -6, /* attached to struct proc */ + EVFILT_TIMER = -7, /* timers */ + EVFILT_MACHPORT = -8, /* Mach portsets */ + EVFILT_FS = -9, /* filesystem events */ + EVFILT_USER = -10, /* User events */ + EVFILT_VM = -12, /* virtual memory events */ + EVFILT_SYSCOUNT = 11 } struct kevent_t { - uintptr_t ident; /* identifier for this event */ - short filter; /* filter for event */ - ushort flags; - uint fflags; - intptr_t data; - void *udata; /* opaque user data identifier */ + uintptr_t ident; /* identifier for this event */ + short filter; /* filter for event */ + ushort flags; + uint fflags; + intptr_t data; + void *udata; /* opaque user data identifier */ } enum { - /* actions */ - EV_ADD = 0x0001, /* add event to kq (implies enable) */ - EV_DELETE = 0x0002, /* delete event from kq */ - EV_ENABLE = 0x0004, /* enable event */ - EV_DISABLE = 0x0008, /* disable event (not reported) */ + /* actions */ + EV_ADD = 0x0001, /* add event to kq (implies enable) */ + EV_DELETE = 0x0002, /* delete event from kq */ + EV_ENABLE = 0x0004, /* enable event */ + EV_DISABLE = 0x0008, /* disable event (not reported) */ - /* flags */ - EV_ONESHOT = 0x0010, /* only report one occurrence */ - EV_CLEAR = 0x0020, /* clear event state after reporting */ - EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */ - EV_DISPATCH = 0x0080, /* disable event after reporting */ + /* flags */ + EV_ONESHOT = 0x0010, /* only report one occurrence */ + EV_CLEAR = 0x0020, /* clear event state after reporting */ + EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */ + EV_DISPATCH = 0x0080, /* disable event after reporting */ - EV_SYSFLAGS = 0xF000, /* reserved by system */ - EV_FLAG1 = 0x2000, /* filter-specific flag */ + EV_SYSFLAGS = 0xF000, /* reserved by system */ + EV_FLAG1 = 0x2000, /* filter-specific flag */ - /* returned values */ - EV_EOF = 0x8000, /* EOF detected */ - EV_ERROR = 0x4000, /* error, data contains errno */ + /* returned values */ + EV_EOF = 0x8000, /* EOF detected */ + EV_ERROR = 0x4000, /* error, data contains errno */ } 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 { - protected int fd; - private Vector!kevent_t events; - private Vector!kevent_t changes; - private size_t changeCount; + protected int fd; + private Array!kevent_t events; + private Array!kevent_t changes; + private size_t changeCount; - /** - * Returns: Maximal event count can be got at a time - * (should be supported by the backend). - */ - override protected @property uint maxEvents() - const pure nothrow @safe @nogc - { - return cast(uint) events.length; - } + /** + * Returns: Maximal event count can be got at a time + * (should be supported by the backend). + */ + override protected @property uint maxEvents() + const pure nothrow @safe @nogc + { + return cast(uint) events.length; + } - this() @nogc - { - super(); + this() @nogc + { + super(); - if ((fd = kqueue()) == -1) - { - throw make!BadLoopException(defaultAllocator, - "kqueue initialization failed"); - } - events = Vector!kevent_t(64, MmapPool.instance); - changes = Vector!kevent_t(64, MmapPool.instance); - } + if ((fd = kqueue()) == -1) + { + throw make!BadLoopException(defaultAllocator, + "kqueue initialization failed"); + } + events = Array!kevent_t(64, MmapPool.instance); + changes = Array!kevent_t(64, MmapPool.instance); + } - /** - * Frees loop internals. - */ - ~this() @nogc - { - close(fd); - } + /** + * Frees loop internals. + */ + ~this() @nogc + { + close(fd); + } - private void set(socket_t socket, short filter, ushort flags) @nogc - { - if (changes.length <= changeCount) - { - changes.length = changeCount + maxEvents; - } - EV_SET(&changes[changeCount], - cast(ulong) socket, - filter, - flags, - 0U, - 0L, - null); - ++changeCount; - } + private void set(socket_t socket, short filter, ushort flags) @nogc + { + if (changes.length <= changeCount) + { + changes.length = changeCount + maxEvents; + } + EV_SET(&changes[changeCount], + cast(ulong) socket, + filter, + flags, + 0U, + 0L, + null); + ++changeCount; + } - /** - * Should be called if the backend configuration changes. - * - * Params: - * watcher = Watcher. - * oldEvents = The events were already set. - * events = The events should be set. - * - * Returns: $(D_KEYWORD true) if the operation was successful. - */ - override protected bool reify(SocketWatcher watcher, - EventMask oldEvents, - EventMask events) @nogc - { - if (events != oldEvents) - { - if (oldEvents & Event.read || oldEvents & Event.accept) - { - set(watcher.socket.handle, EVFILT_READ, EV_DELETE); - } - if (oldEvents & Event.write) - { - set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE); - } - } - if (events & (Event.read | events & Event.accept)) - { - set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE); - } - if (events & Event.write) - { - set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH); - } - return true; - } + /** + * Should be called if the backend configuration changes. + * + * Params: + * watcher = Watcher. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + override protected bool reify(SocketWatcher watcher, + EventMask oldEvents, + EventMask events) @nogc + { + if (events != oldEvents) + { + if (oldEvents & Event.read || oldEvents & Event.accept) + { + set(watcher.socket.handle, EVFILT_READ, EV_DELETE); + } + if (oldEvents & Event.write) + { + set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE); + } + } + if (events & (Event.read | events & Event.accept)) + { + set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE); + } + if (events & Event.write) + { + set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH); + } + return true; + } - /** - * Does the actual polling. - */ - protected override void poll() @nogc - { - timespec ts; - blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec); + /** + * Does the actual polling. + */ + protected override void poll() @nogc + { + timespec ts; + blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec); - if (changeCount > maxEvents) - { - events.length = changes.length; - } + if (changeCount > maxEvents) + { + events.length = changes.length; + } - auto eventCount = kevent(fd, - changes.get().ptr, - cast(int) changeCount, - events.get().ptr, - maxEvents, - &ts); - changeCount = 0; + auto eventCount = kevent(fd, + changes.get().ptr, + cast(int) changeCount, + events.get().ptr, + maxEvents, + &ts); + changeCount = 0; - if (eventCount < 0) - { - if (errno != EINTR) - { - throw defaultAllocator.make!BadLoopException(); - } - return; - } + if (eventCount < 0) + { + if (errno != EINTR) + { + throw defaultAllocator.make!BadLoopException(); + } + return; + } - for (int i; i < eventCount; ++i) - { - assert(connections.length > events[i].ident); + for (int i; i < eventCount; ++i) + { + assert(connections.length > events[i].ident); - auto transport = cast(StreamTransport) connections[events[i].ident]; - // If it is a ConnectionWatcher. Accept connections. - if (transport is null) - { - auto connection = cast(ConnectionWatcher) connections[events[i].ident]; - assert(connection !is null); + auto transport = cast(StreamTransport) connections[events[i].ident]; + // If it is a ConnectionWatcher. Accept connections. + if (transport is null) + { + auto connection = cast(ConnectionWatcher) connections[events[i].ident]; + assert(connection !is null); - acceptConnections(connection); - } - else if (events[i].flags & EV_ERROR) - { - kill(transport); - } - else if (events[i].filter == EVFILT_READ) - { - SocketException exception; - try - { - ptrdiff_t received; - do - { - received = transport.socket.receive(transport.output[]); - transport.output += received; - } - while (received); - } - catch (SocketException e) - { - exception = e; - } - if (transport.socket.disconnected) - { - kill(transport, exception); - } - else if (transport.output.length) - { - pendings.enqueue(transport); - } - } - else if (events[i].filter == EVFILT_WRITE) - { - transport.writeReady = true; - if (transport.input.length) - { - feed(transport); - } - } - } - } + acceptConnections(connection); + } + else if (events[i].flags & EV_ERROR) + { + kill(transport); + } + else if (events[i].filter == EVFILT_READ) + { + SocketException exception; + try + { + ptrdiff_t received; + do + { + received = transport.socket.receive(transport.output[]); + transport.output += received; + } + while (received); + } + catch (SocketException e) + { + exception = e; + } + if (transport.socket.disconnected) + { + kill(transport, exception); + } + else if (transport.output.length) + { + pendings.enqueue(transport); + } + } + else if (events[i].filter == EVFILT_WRITE) + { + transport.writeReady = true; + if (transport.input.length) + { + feed(transport); + } + } + } + } - /** - * Returns: The blocking time. - */ - override protected @property inout(Duration) blockTime() - inout @nogc @safe pure nothrow - { - return min(super.blockTime, 1.dur!"seconds"); - } + /** + * Returns: The blocking time. + */ + override protected @property inout(Duration) blockTime() + inout @nogc @safe pure nothrow + { + return min(super.blockTime, 1.dur!"seconds"); + } - /** - * If the transport couldn't send the data, the further sending should - * be handled by the event loop. - * - * Params: - * transport = Transport. - * exception = Exception thrown on sending. - * - * Returns: $(D_KEYWORD true) if the operation could be successfully - * completed or scheduled, $(D_KEYWORD false) otherwise (the - * transport will be destroyed then). - */ - protected override bool feed(StreamTransport transport, - SocketException exception = null) @nogc - { - if (!super.feed(transport, exception)) - { - return false; - } - if (!transport.writeReady) - { - set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH); - return true; - } - return false; - } + /** + * If the transport couldn't send the data, the further sending should + * be handled by the event loop. + * + * Params: + * transport = Transport. + * exception = Exception thrown on sending. + * + * Returns: $(D_KEYWORD true) if the operation could be successfully + * completed or scheduled, $(D_KEYWORD false) otherwise (the + * transport will be destroyed then). + */ + protected override bool feed(StreamTransport transport, + SocketException exception = null) @nogc + { + if (!super.feed(transport, exception)) + { + return false; + } + if (!transport.writeReady) + { + set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH); + return true; + } + return false; + } } diff --git a/source/tanya/async/event/selector.d b/source/tanya/async/event/selector.d index 13254ce..7194756 100644 --- a/source/tanya/async/event/selector.d +++ b/source/tanya/async/event/selector.d @@ -17,7 +17,7 @@ import tanya.async.protocol; import tanya.async.transport; import tanya.async.watcher; import tanya.container.buffer; -import tanya.container.vector; +import tanya.container.array; import tanya.memory; import tanya.memory.mmappool; import tanya.network.socket; @@ -27,371 +27,374 @@ import tanya.network.socket; */ 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. - package bool writeReady; + /// Received notification that the underlying socket is write-ready. + package bool writeReady; - /** - * Params: - * loop = Event loop. - * socket = Socket. - * - * Precondition: $(D_INLINECODE loop !is null && socket !is null) - */ - this(SelectorLoop loop, ConnectedSocket socket) @nogc - in - { - assert(loop !is null); - } - body - { - super(socket); - this.loop = loop; - output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); - input = WriteBuffer!ubyte(8192, MmapPool.instance); - active = true; - } + /** + * Params: + * loop = Event loop. + * socket = Socket. + * + * Precondition: $(D_INLINECODE loop !is null && socket !is null) + */ + this(SelectorLoop loop, ConnectedSocket socket) @nogc + in + { + assert(loop !is null); + } + body + { + super(socket); + this.loop = loop; + output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); + input = WriteBuffer!ubyte(8192, MmapPool.instance); + active = true; + } - /** - * Returns: Socket. - * - * Postcondition: $(D_INLINECODE socket !is null) - */ - override @property ConnectedSocket socket() pure nothrow @safe @nogc - out (socket) - { - assert(socket !is null); - } - body - { - return cast(ConnectedSocket) socket_; - } + /** + * Returns: Socket. + * + * Postcondition: $(D_INLINECODE socket !is null) + */ + override @property ConnectedSocket socket() pure nothrow @safe @nogc + out (socket) + { + assert(socket !is null); + } + body + { + return cast(ConnectedSocket) socket_; + } - private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc - in - { - assert(socket !is null); - } - body - { - socket_ = socket; - } + private @property void socket(ConnectedSocket socket) + pure nothrow @safe @nogc + in + { + assert(socket !is null); + } + body + { + socket_ = socket; + } - /** - * Returns: Application protocol. - */ - @property Protocol protocol() pure nothrow @safe @nogc - { - return protocol_; - } + /** + * Returns: Application protocol. + */ + @property Protocol protocol() pure nothrow @safe @nogc + { + return protocol_; + } - /** - * Switches the protocol. - * - * The protocol is deallocated by the event loop, it should currently be - * allocated with $(D_PSYMBOL MmapPool). - * - * Params: - * protocol = Application protocol. - * - * Precondition: $(D_INLINECODE protocol !is null) - */ - @property void protocol(Protocol protocol) pure nothrow @safe @nogc - in - { - assert(protocol !is null); - } - body - { - protocol_ = protocol; - } + /** + * Switches the protocol. + * + * The protocol is deallocated by the event loop, it should currently be + * allocated with $(D_PSYMBOL MmapPool). + * + * Params: + * protocol = Application protocol. + * + * Precondition: $(D_INLINECODE protocol !is null) + */ + @property void protocol(Protocol protocol) pure nothrow @safe @nogc + in + { + assert(protocol !is null); + } + body + { + protocol_ = protocol; + } - /** - * Returns $(D_PARAM true) if the transport is closing or closed. - */ - bool isClosing() const pure nothrow @safe @nogc - { - return closing; - } + /** + * Returns $(D_PARAM true) if the transport is closing or closed. + */ + bool isClosing() const pure nothrow @safe @nogc + { + return closing; + } - /** - * Close the transport. - * - * Buffered data will be flushed. No more data will be received. - */ - void close() @nogc - { - closing = true; - loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write)); - } + /** + * Close the transport. + * + * Buffered data will be flushed. No more data will be received. + */ + void close() @nogc + { + closing = true; + loop.reify(this, + EventMask(Event.read, Event.write), + EventMask(Event.write)); + } - /** - * Invokes the watcher callback. - */ - override void invoke() @nogc - { - if (output.length) - { - protocol.received(output[0 .. $]); - output.clear(); - if (isClosing() && input.length == 0) - { - loop.kill(this); - } - } - else - { - protocol.disconnected(exception); - MmapPool.instance.dispose(protocol_); - defaultAllocator.dispose(exception); - active = false; - } - } + /** + * Invokes the watcher callback. + */ + override void invoke() @nogc + { + if (output.length) + { + protocol.received(output[0 .. $]); + output.clear(); + if (isClosing() && input.length == 0) + { + loop.kill(this); + } + } + else + { + protocol.disconnected(exception); + MmapPool.instance.dispose(protocol_); + defaultAllocator.dispose(exception); + active = false; + } + } - /** - * Write some data to the transport. - * - * Params: - * data = Data to send. - */ - void write(ubyte[] data) @nogc - { - if (!data.length) - { - return; - } - // Try to write if the socket is write ready. - if (writeReady) - { - ptrdiff_t sent; - SocketException exception; - try - { - sent = socket.send(data); - if (sent == 0) - { - writeReady = false; - } - } - catch (SocketException e) - { - writeReady = false; - exception = e; - } - if (sent < data.length) - { - input ~= data[sent..$]; - loop.feed(this, exception); - } - } - else - { - input ~= data; - } - } + /** + * Write some data to the transport. + * + * Params: + * data = Data to send. + */ + void write(ubyte[] data) @nogc + { + if (!data.length) + { + return; + } + // Try to write if the socket is write ready. + if (writeReady) + { + ptrdiff_t sent; + SocketException exception; + try + { + sent = socket.send(data); + if (sent == 0) + { + writeReady = false; + } + } + catch (SocketException e) + { + writeReady = false; + exception = e; + } + if (sent < data.length) + { + input ~= data[sent..$]; + loop.feed(this, exception); + } + } + else + { + input ~= data; + } + } } abstract class SelectorLoop : Loop { - /// Pending connections. - protected Vector!SocketWatcher connections; + /// Pending connections. + protected Array!SocketWatcher connections; - this() @nogc - { - super(); - connections = Vector!SocketWatcher(maxEvents, MmapPool.instance); - } + this() @nogc + { + super(); + connections = Array!SocketWatcher(maxEvents, MmapPool.instance); + } - ~this() @nogc - { - foreach (ref connection; connections) - { - // We want to free only the transports. ConnectionWatcher are created by the - // user and should be freed by himself. - if (cast(StreamTransport) connection !is null) - { - MmapPool.instance.dispose(connection); - } - } - } + ~this() @nogc + { + foreach (ref connection; connections) + { + // We want to free only the transports. ConnectionWatcher are + // created by the user and should be freed by himself. + if (cast(StreamTransport) connection !is null) + { + MmapPool.instance.dispose(connection); + } + } + } - /** - * Should be called if the backend configuration changes. - * - * Params: - * watcher = Watcher. - * oldEvents = The events were already set. - * events = The events should be set. - * - * Returns: $(D_KEYWORD true) if the operation was successful. - */ - override abstract protected bool reify(SocketWatcher watcher, - EventMask oldEvents, - EventMask events) @nogc; + /** + * Should be called if the backend configuration changes. + * + * Params: + * watcher = Watcher. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + override abstract protected bool reify(SocketWatcher watcher, + EventMask oldEvents, + EventMask events) @nogc; - /** - * Kills the watcher and closes the connection. - * - * Params: - * transport = Transport. - * exception = Occurred exception. - */ - protected void kill(StreamTransport transport, - SocketException exception = null) @nogc - in - { - assert(transport !is null); - } - body - { - transport.socket.shutdown(); - defaultAllocator.dispose(transport.socket); - transport.exception = exception; - pendings.enqueue(transport); - } + /** + * Kills the watcher and closes the connection. + * + * Params: + * transport = Transport. + * exception = Occurred exception. + */ + protected void kill(StreamTransport transport, + SocketException exception = null) @nogc + in + { + assert(transport !is null); + } + body + { + transport.socket.shutdown(); + defaultAllocator.dispose(transport.socket); + transport.exception = exception; + pendings.enqueue(transport); + } - /** - * If the transport couldn't send the data, the further sending should - * be handled by the event loop. - * - * Params: - * transport = Transport. - * exception = Exception thrown on sending. - * - * Returns: $(D_KEYWORD true) if the operation could be successfully - * completed or scheduled, $(D_KEYWORD false) otherwise (the - * transport will be destroyed then). - */ - protected bool feed(StreamTransport transport, - SocketException exception = null) @nogc - in - { - assert(transport !is null); - } - body - { - while (transport.input.length && transport.writeReady) - { - try - { - ptrdiff_t sent = transport.socket.send(transport.input[]); - if (sent == 0) - { - transport.writeReady = false; - } - else - { - transport.input += sent; - } - } - catch (SocketException e) - { - exception = e; - transport.writeReady = false; - } - } - if (exception !is null) - { - kill(transport, exception); - return false; - } - if (transport.input.length == 0 && transport.isClosing()) - { - kill(transport); - } - return true; - } + /** + * If the transport couldn't send the data, the further sending should + * be handled by the event loop. + * + * Params: + * transport = Transport. + * exception = Exception thrown on sending. + * + * Returns: $(D_KEYWORD true) if the operation could be successfully + * completed or scheduled, $(D_KEYWORD false) otherwise (the + * transport will be destroyed then). + */ + protected bool feed(StreamTransport transport, + SocketException exception = null) @nogc + in + { + assert(transport !is null); + } + body + { + while (transport.input.length && transport.writeReady) + { + try + { + ptrdiff_t sent = transport.socket.send(transport.input[]); + if (sent == 0) + { + transport.writeReady = false; + } + else + { + transport.input += sent; + } + } + catch (SocketException e) + { + exception = e; + transport.writeReady = false; + } + } + if (exception !is null) + { + kill(transport, exception); + return false; + } + if (transport.input.length == 0 && transport.isClosing()) + { + kill(transport); + } + return true; + } - /** - * Start watching. - * - * Params: - * watcher = Watcher. - */ - override void start(ConnectionWatcher watcher) @nogc - { - if (watcher.active) - { - return; - } + /** + * Start watching. + * + * Params: + * watcher = Watcher. + */ + override void start(ConnectionWatcher watcher) @nogc + { + if (watcher.active) + { + return; + } - if (connections.length <= watcher.socket) - { - connections.length = watcher.socket.handle + maxEvents / 2; - } - connections[watcher.socket.handle] = watcher; + if (connections.length <= watcher.socket) + { + connections.length = watcher.socket.handle + maxEvents / 2; + } + connections[watcher.socket.handle] = watcher; - super.start(watcher); - } + super.start(watcher); + } - /** - * Accept incoming connections. - * - * Params: - * connection = Connection watcher ready to accept. - */ - package void acceptConnections(ConnectionWatcher connection) @nogc - in - { - assert(connection !is null); - } - body - { - while (true) - { - ConnectedSocket client; - try - { - client = (cast(StreamSocket) connection.socket).accept(); - } - catch (SocketException e) - { - defaultAllocator.dispose(e); - break; - } - if (client is null) - { - break; - } + /** + * Accept incoming connections. + * + * Params: + * connection = Connection watcher ready to accept. + */ + package void acceptConnections(ConnectionWatcher connection) @nogc + in + { + assert(connection !is null); + } + body + { + while (true) + { + ConnectedSocket client; + try + { + client = (cast(StreamSocket) connection.socket).accept(); + } + catch (SocketException e) + { + defaultAllocator.dispose(e); + break; + } + if (client is null) + { + break; + } - StreamTransport transport; + StreamTransport transport; - if (connections.length > client.handle) - { - transport = cast(StreamTransport) connections[client.handle]; - } - else - { - connections.length = client.handle + maxEvents / 2; - } - if (transport is null) - { - transport = MmapPool.instance.make!StreamTransport(this, client); - connections[client.handle] = transport; - } - else - { - transport.socket = client; - } + if (connections.length > client.handle) + { + transport = cast(StreamTransport) connections[client.handle]; + } + else + { + connections.length = client.handle + maxEvents / 2; + } + if (transport is null) + { + transport = MmapPool.instance.make!StreamTransport(this, client); + connections[client.handle] = transport; + } + else + { + transport.socket = client; + } - reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write)); - connection.incoming.enqueue(transport); - } + reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write)); + connection.incoming.enqueue(transport); + } - if (!connection.incoming.empty) - { - pendings.enqueue(connection); - } - } + if (!connection.incoming.empty) + { + pendings.enqueue(connection); + } + } } diff --git a/source/tanya/async/iocp.d b/source/tanya/async/iocp.d index 0dfcbe8..bd191d3 100644 --- a/source/tanya/async/iocp.d +++ b/source/tanya/async/iocp.d @@ -21,12 +21,12 @@ import core.sys.windows.windef; */ class State { - /// For internal use by Windows API. - align(1) OVERLAPPED overlapped; + /// For internal use by Windows API. + align(1) OVERLAPPED overlapped; - /// File/socket handle. - HANDLE handle; + /// File/socket handle. + HANDLE handle; - /// For keeping events or event masks. - int event; + /// For keeping events or event masks. + int event; } diff --git a/source/tanya/async/loop.d b/source/tanya/async/loop.d index 54b5238..04b1422 100644 --- a/source/tanya/async/loop.d +++ b/source/tanya/async/loop.d @@ -15,50 +15,50 @@ * * class EchoProtocol : TransmissionControlProtocol * { - * private DuplexTransport transport; + * private DuplexTransport transport; * - * void received(in ubyte[] data) @nogc - * { - * transport.write(data); - * } + * void received(in ubyte[] data) @nogc + * { + * transport.write(data); + * } * - * void connected(DuplexTransport transport) @nogc - * { - * this.transport = transport; - * } + * void connected(DuplexTransport transport) @nogc + * { + * this.transport = transport; + * } * - * void disconnected(SocketException e) @nogc - * { - * } + * void disconnected(SocketException e) @nogc + * { + * } * } * * 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) - * { - * auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET); - * } - * else - * { - * auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET); - * sock.blocking = false; - * } + * version (Windows) + * { + * auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET); + * } + * else + * { + * auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET); + * sock.blocking = false; + * } * - * sock.bind(address); - * sock.listen(5); + * sock.bind(address); + * sock.listen(5); * - * auto io = defaultAllocator.make!ConnectionWatcher(sock); - * io.setProtocol!EchoProtocol; + * auto io = defaultAllocator.make!ConnectionWatcher(sock); + * io.setProtocol!EchoProtocol; * - * defaultLoop.start(io); - * defaultLoop.run(); + * defaultLoop.start(io); + * defaultLoop.run(); * - * sock.shutdown(); - * defaultAllocator.dispose(io); - * defaultAllocator.dispose(sock); - * defaultAllocator.dispose(address); + * sock.shutdown(); + * defaultAllocator.dispose(io); + * defaultAllocator.dispose(sock); + * defaultAllocator.dispose(address); * } * --- */ @@ -81,33 +81,33 @@ version (DisableBackends) } else version (linux) { - import tanya.async.event.epoll; - version = Epoll; + import tanya.async.event.epoll; + version = Epoll; } else version (Windows) { - import tanya.async.event.iocp; - version = IOCP; + import tanya.async.event.iocp; + version = IOCP; } else version (OSX) { - version = Kqueue; + version = Kqueue; } else version (iOS) { - version = Kqueue; + version = Kqueue; } else version (FreeBSD) { - version = Kqueue; + version = Kqueue; } else version (OpenBSD) { - version = Kqueue; + version = Kqueue; } else version (DragonFlyBSD) { - version = Kqueue; + version = Kqueue; } /** @@ -115,11 +115,11 @@ else version (DragonFlyBSD) */ enum Event : uint { - none = 0x00, /// No events. - read = 0x01, /// Non-blocking read call. - write = 0x02, /// Non-blocking write call. - accept = 0x04, /// Connection made. - error = 0x80000000, /// Sent when an error occurs. + none = 0x00, /// No events. + read = 0x01, /// Non-blocking read call. + write = 0x02, /// Non-blocking write call. + accept = 0x04, /// Connection made. + error = 0x80000000, /// Sent when an error occurs. } alias EventMask = BitFlags!Event; @@ -129,150 +129,150 @@ alias EventMask = BitFlags!Event; */ abstract class Loop { - private bool done; + private bool done; - /// Pending watchers. - protected Queue!Watcher pendings; + /// Pending watchers. + protected Queue!Watcher pendings; - /** - * Returns: Maximal event count can be got at a time - * (should be supported by the backend). - */ - protected @property uint maxEvents() - const pure nothrow @safe @nogc - { - return 128U; - } + /** + * Returns: Maximal event count can be got at a time + * (should be supported by the backend). + */ + protected @property uint maxEvents() + const pure nothrow @safe @nogc + { + return 128U; + } - /** - * Initializes the loop. - */ - this() @nogc - { - pendings = Queue!Watcher(MmapPool.instance); - } + /** + * Initializes the loop. + */ + this() @nogc + { + pendings = Queue!Watcher(MmapPool.instance); + } - /** - * Frees loop internals. - */ - ~this() @nogc - { - foreach (w; pendings) - { - MmapPool.instance.dispose(w); - } - } + /** + * Frees loop internals. + */ + ~this() @nogc + { + foreach (w; pendings) + { + MmapPool.instance.dispose(w); + } + } - /** - * Starts the loop. - */ - void run() @nogc - { - done = false; - do - { - poll(); + /** + * Starts the loop. + */ + void run() @nogc + { + done = false; + do + { + poll(); - // Invoke pendings - foreach (ref w; pendings) - { - w.invoke(); - } - } - while (!done); - } + // Invoke pendings + foreach (ref w; pendings) + { + w.invoke(); + } + } + while (!done); + } - /** - * Break out of the loop. - */ - void unloop() @safe pure nothrow @nogc - { - done = true; - } + /** + * Break out of the loop. + */ + void unloop() @safe pure nothrow @nogc + { + done = true; + } - /** - * Start watching. - * - * Params: - * watcher = Watcher. - */ - void start(ConnectionWatcher watcher) @nogc - { - if (watcher.active) - { - return; - } - watcher.active = true; + /** + * Start watching. + * + * Params: + * watcher = Watcher. + */ + void start(ConnectionWatcher watcher) @nogc + { + if (watcher.active) + { + return; + } + watcher.active = true; - reify(watcher, EventMask(Event.none), EventMask(Event.accept)); - } + reify(watcher, EventMask(Event.none), EventMask(Event.accept)); + } - /** - * Stop watching. - * - * Params: - * watcher = Watcher. - */ - void stop(ConnectionWatcher watcher) @nogc - { - if (!watcher.active) - { - return; - } - watcher.active = false; + /** + * Stop watching. + * + * Params: + * watcher = Watcher. + */ + void stop(ConnectionWatcher watcher) @nogc + { + if (!watcher.active) + { + return; + } + 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. - * - * Params: - * watcher = Watcher. - * oldEvents = The events were already set. - * events = The events should be set. - * - * Returns: $(D_KEYWORD true) if the operation was successful. - */ - abstract protected bool reify(SocketWatcher watcher, - EventMask oldEvents, - EventMask events) @nogc; + /** + * Should be called if the backend configuration changes. + * + * Params: + * watcher = Watcher. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + abstract protected bool reify(SocketWatcher watcher, + EventMask oldEvents, + EventMask events) @nogc; - /** - * Returns: The blocking time. - */ - protected @property inout(Duration) blockTime() - inout @safe pure nothrow @nogc - { - // Don't block if we have to do. - return pendings.empty ? blockTime_ : Duration.zero; - } + /** + * Returns: The blocking time. + */ + protected @property inout(Duration) blockTime() + inout @safe pure nothrow @nogc + { + // Don't block if we have to do. + return pendings.empty ? blockTime_ : Duration.zero; + } - /** - * Sets the blocking time for IO watchers. - * - * Params: - * blockTime = The blocking time. Cannot be larger than - * $(D_PSYMBOL maxBlockTime). - */ - protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc - in - { - assert(blockTime <= 1.dur!"hours", "Too long to wait."); - assert(!blockTime.isNegative); - } - body - { - blockTime_ = blockTime; - } + /** + * Sets the blocking time for IO watchers. + * + * Params: + * blockTime = The blocking time. Cannot be larger than + * $(D_PSYMBOL maxBlockTime). + */ + protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc + in + { + assert(blockTime <= 1.dur!"hours", "Too long to wait."); + assert(!blockTime.isNegative); + } + body + { + blockTime_ = blockTime; + } - /** - * Does the actual polling. - */ - abstract protected void poll() @nogc; + /** + * Does the actual polling. + */ + abstract protected void poll() @nogc; - /// Maximal block time. - protected Duration blockTime_ = 1.dur!"minutes"; + /// Maximal block time. + protected Duration blockTime_ = 1.dur!"minutes"; } /** @@ -280,17 +280,17 @@ abstract class Loop */ class BadLoopException : Exception { - /** - * Params: - * 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 file = __FILE__, size_t line = __LINE__, Throwable next = null) - pure nothrow const @safe @nogc - { - super("Event loop cannot be initialized.", file, line, next); - } + /** + * Params: + * 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 file = __FILE__, size_t line = __LINE__, Throwable next = null) + pure nothrow const @safe @nogc + { + super("Event loop cannot be initialized.", file, line, next); + } } /** @@ -302,24 +302,24 @@ class BadLoopException : Exception */ @property Loop defaultLoop() @nogc { - if (defaultLoop_ !is null) - { - return defaultLoop_; - } - version (Epoll) - { - defaultLoop_ = MmapPool.instance.make!EpollLoop; - } - else version (IOCP) - { - defaultLoop_ = MmapPool.instance.make!IOCPLoop; - } - else version (Kqueue) - { - import tanya.async.event.kqueue; - defaultLoop_ = MmapPool.instance.make!KqueueLoop; - } - return defaultLoop_; + if (defaultLoop_ !is null) + { + return defaultLoop_; + } + version (Epoll) + { + defaultLoop_ = MmapPool.instance.make!EpollLoop; + } + else version (IOCP) + { + defaultLoop_ = MmapPool.instance.make!IOCPLoop; + } + else version (Kqueue) + { + import tanya.async.event.kqueue; + defaultLoop_ = MmapPool.instance.make!KqueueLoop; + } + return defaultLoop_; } /** @@ -331,16 +331,16 @@ class BadLoopException : Exception * your implementation to this property. * * Params: - * loop = The event loop. + * loop = The event loop. */ @property void defaultLoop(Loop loop) @nogc in { - assert(loop !is null); + assert(loop !is null); } body { - defaultLoop_ = loop; + defaultLoop_ = loop; } private Loop defaultLoop_; diff --git a/source/tanya/async/protocol.d b/source/tanya/async/protocol.d index 28e90e0..2498d45 100644 --- a/source/tanya/async/protocol.d +++ b/source/tanya/async/protocol.d @@ -18,28 +18,28 @@ import tanya.async.transport; */ interface Protocol { - /** - * Params: - * data = Read data. - */ - void received(in ubyte[] data) @nogc; + /** + * Params: + * data = Read data. + */ + void received(in ubyte[] data) @nogc; - /** - * Called when a connection is made. - * - * Params: - * transport = Protocol transport. - */ - void connected(DuplexTransport transport) @nogc; + /** + * Called when a connection is made. + * + * Params: + * transport = Protocol transport. + */ + void connected(DuplexTransport transport) @nogc; - /** - * Called when a connection is lost. - * - * Params: - * exception = $(D_PSYMBOL Exception) if an error caused - * the disconnect, $(D_KEYWORD null) otherwise. - */ - void disconnected(SocketException exception) @nogc; + /** + * Called when a connection is lost. + * + * Params: + * exception = $(D_PSYMBOL Exception) if an error caused + * the disconnect, $(D_KEYWORD null) otherwise. + */ + void disconnected(SocketException exception) @nogc; } /** diff --git a/source/tanya/async/transport.d b/source/tanya/async/transport.d index 4550522..2d5fbe7 100644 --- a/source/tanya/async/transport.d +++ b/source/tanya/async/transport.d @@ -32,13 +32,13 @@ interface ReadTransport : Transport */ interface WriteTransport : Transport { - /** - * Write some data to the transport. - * - * Params: - * data = Data to send. - */ - void write(ubyte[] data) @nogc; + /** + * Write some data to the transport. + * + * Params: + * data = Data to send. + */ + void write(ubyte[] data) @nogc; } /** @@ -46,46 +46,46 @@ interface WriteTransport : Transport */ interface DuplexTransport : ReadTransport, WriteTransport { - /** - * Returns: Application protocol. - * - * Postcondition: $(D_INLINECODE protocol !is null) - */ - @property Protocol protocol() pure nothrow @safe @nogc - out (protocol) - { - assert(protocol !is null); - } + /** + * Returns: Application protocol. + * + * Postcondition: $(D_INLINECODE protocol !is null) + */ + @property Protocol protocol() pure nothrow @safe @nogc + out (protocol) + { + assert(protocol !is null); + } - /** - * Switches the protocol. - * - * The protocol is deallocated by the event loop, it should currently be - * allocated with $(D_PSYMBOL MmapPool). - * - * Params: - * protocol = Application protocol. - * - * Precondition: $(D_INLINECODE protocol !is null) - */ - @property void protocol(Protocol protocol) pure nothrow @safe @nogc - in - { - assert(protocol !is null); - } + /** + * Switches the protocol. + * + * The protocol is deallocated by the event loop, it should currently be + * allocated with $(D_PSYMBOL MmapPool). + * + * Params: + * protocol = Application protocol. + * + * Precondition: $(D_INLINECODE protocol !is null) + */ + @property void protocol(Protocol protocol) pure nothrow @safe @nogc + in + { + assert(protocol !is null); + } - /** - * Returns $(D_PARAM true) if the transport is closing or closed. - */ - bool isClosing() const pure nothrow @safe @nogc; + /** + * Returns $(D_PARAM true) if the transport is closing or closed. + */ + bool isClosing() const pure nothrow @safe @nogc; - /** - * Close the transport. - * - * Buffered data will be flushed. No more data will be received. - */ - void close() @nogc; + /** + * Close the transport. + * + * Buffered data will be flushed. No more data will be received. + */ + void close() @nogc; } /** @@ -93,8 +93,8 @@ interface DuplexTransport : ReadTransport, WriteTransport */ interface SocketTransport : Transport { - /** - * Returns: Socket. - */ - @property Socket socket() pure nothrow @safe @nogc; + /** + * Returns: Socket. + */ + @property Socket socket() pure nothrow @safe @nogc; } diff --git a/source/tanya/async/watcher.d b/source/tanya/async/watcher.d index 9756d99..048c5de 100644 --- a/source/tanya/async/watcher.d +++ b/source/tanya/async/watcher.d @@ -27,13 +27,13 @@ import tanya.network.socket; */ abstract class Watcher { - /// Whether the watcher is active. - bool active; + /// Whether the watcher is active. + bool active; - /** - * Invoke some action on event. - */ - void invoke() @nogc; + /** + * Invoke some action on event. + */ + void invoke() @nogc; } /** @@ -41,32 +41,32 @@ abstract class Watcher */ abstract class SocketWatcher : Watcher { - /// Watched socket. - protected Socket socket_; + /// Watched socket. + protected Socket socket_; - /** - * Params: - * socket = Socket. - * - * Precondition: $(D_INLINECODE socket !is null) - */ - this(Socket socket) pure nothrow @safe @nogc - in - { - assert(socket !is null); - } - body - { - socket_ = socket; - } + /** + * Params: + * socket = Socket. + * + * Precondition: $(D_INLINECODE socket !is null) + */ + this(Socket socket) pure nothrow @safe @nogc + in + { + assert(socket !is null); + } + body + { + socket_ = socket; + } - /** - * Returns: Socket. - */ - @property Socket socket() pure nothrow @safe @nogc - { - return socket_; - } + /** + * Returns: Socket. + */ + @property Socket socket() pure nothrow @safe @nogc + { + return socket_; + } } /** @@ -74,44 +74,44 @@ abstract class SocketWatcher : Watcher */ class ConnectionWatcher : SocketWatcher { - /// Incoming connection queue. - Queue!DuplexTransport incoming; + /// Incoming connection queue. + Queue!DuplexTransport incoming; - private Protocol delegate() @nogc protocolFactory; + private Protocol delegate() @nogc protocolFactory; - /** - * Params: - * socket = Socket. - */ - this(Socket socket) @nogc - { - super(socket); - incoming = Queue!DuplexTransport(MmapPool.instance); - } + /** + * Params: + * socket = Socket. + */ + this(Socket socket) @nogc + { + super(socket); + incoming = Queue!DuplexTransport(MmapPool.instance); + } - /** - * Params: - * P = Protocol should be used. - */ - void setProtocol(P : Protocol)() @nogc - { - this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P; - } + /** + * Params: + * P = Protocol should be used. + */ + void setProtocol(P : Protocol)() @nogc + { + this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P; + } - /** - * Invokes new connection callback. - */ - override void invoke() @nogc - in - { - assert(protocolFactory !is null, "Protocol isn't set."); - } - body - { - foreach (transport; incoming) - { - transport.protocol = protocolFactory(); - transport.protocol.connected(transport); - } - } + /** + * Invokes new connection callback. + */ + override void invoke() @nogc + in + { + assert(protocolFactory !is null, "Protocol isn't set."); + } + body + { + foreach (transport; incoming) + { + transport.protocol = protocolFactory(); + transport.protocol.connected(transport); + } + } } diff --git a/source/tanya/container/array.d b/source/tanya/container/array.d new file mode 100644 index 0000000..647d510 --- /dev/null +++ b/source/tanya/container/array.d @@ -0,0 +1,1646 @@ +/* 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/. */ + +/** + * Single-dimensioned array. + * + * 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.container.array; + +import core.checkedint; +import core.exception; +import std.algorithm.comparison; +import std.algorithm.mutation; +import std.conv; +import std.range.primitives; +import std.meta; +import std.traits; +import tanya.memory; + +deprecated("Use tanya.container.array instead.") +alias Vector = Array; + +/** + * Random-access range for the $(D_PSYMBOL Array). + * + * Params: + * E = Element type. + */ +struct Range(E) +{ + private E* begin, end; + private alias ContainerType = CopyConstness!(E, Array!(Unqual!E)); + private ContainerType* container; + + invariant + { + assert(this.begin <= this.end); + assert(this.container !is null); + assert(this.begin >= this.container.data); + assert(this.end <= this.container.data + this.container.length); + } + + private this(ref ContainerType container, E* begin, E* end) @trusted + in + { + assert(begin <= end); + assert(begin >= container.data); + assert(end <= container.data + container.length); + } + body + { + this.container = &container; + this.begin = begin; + this.end = end; + } + + @disable this(); + + @property Range save() + { + return this; + } + + @property bool empty() const + { + return this.begin == this.end; + } + + @property size_t length() const + { + return this.end - this.begin; + } + + alias opDollar = length; + + @property ref inout(E) front() inout + in + { + assert(!empty); + } + body + { + return *this.begin; + } + + @property ref inout(E) back() inout @trusted + in + { + assert(!empty); + } + body + { + return *(this.end - 1); + } + + void popFront() @trusted + in + { + assert(!empty); + } + body + { + ++this.begin; + } + + void popBack() @trusted + in + { + assert(!empty); + } + body + { + --this.end; + } + + ref inout(E) opIndex(const size_t i) inout @trusted + in + { + assert(i < length); + } + body + { + return *(this.begin + i); + } + + Range opIndex() + { + return typeof(return)(*this.container, this.begin, this.end); + } + + Range!(const E) opIndex() const + { + return typeof(return)(*this.container, this.begin, this.end); + } + + Range opSlice(const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(*this.container, this.begin + i, this.begin + j); + } + + Range!(const E) opSlice(const size_t i, const size_t j) const @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(*this.container, this.begin + i, this.begin + j); + } + + inout(E[]) get() inout @trusted + { + return this.begin[0 .. length]; + } +} + +/** + * One dimensional array. + * + * Params: + * T = Content type. + */ +struct Array(T) +{ + private size_t length_; + private T* data; + private size_t capacity_; + + invariant + { + assert(this.length_ <= this.capacity_); + assert(this.capacity_ == 0 || this.data !is null); + } + + /** + * Creates a new $(D_PSYMBOL Array) with the elements from a static array. + * + * Params: + * R = Static array size. + * init = Values to initialize the array with. + * allocator = Allocator. + */ + this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator) + { + this(allocator); + insertBack!(T[])(init[]); + } + + /** + * Creates a new $(D_PSYMBOL Array) with the elements from an input range. + * + * Params: + * R = Type of the initial range. + * init = Values to initialize the array with. + * allocator = Allocator. + */ + this(R)(R init, shared Allocator allocator = defaultAllocator) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + this(allocator); + insertBack(init); + } + + /** + * Initializes this array from another one. + * + * If $(D_PARAM init) is passed by value, it won't be copied, but moved. + * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator), + * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s + * storage, otherwise, the storage will be allocated with + * $(D_PARAM allocator) and all elements will be moved; + * $(D_PARAM init) will be destroyed at the end. + * + * If $(D_PARAM init) is passed by reference, it will be copied. + * + * Params: + * R = Source array type. + * init = Source array. + * allocator = Allocator. + */ + this(R)(ref R init, shared Allocator allocator = defaultAllocator) + if (is(Unqual!R == Array)) + { + this(allocator); + insertBack(init[]); + } + + /// Ditto. + this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted + if (is(R == Array)) + { + this(allocator); + if (allocator is init.allocator) + { + // Just steal all references and the allocator. + this.data = init.data; + this.length_ = init.length_; + this.capacity_ = init.capacity_; + + // Reset the source array, so it can't destroy the moved storage. + init.length_ = init.capacity_ = 0; + init.data = null; + } + else + { + // Move each element. + reserve(init.length_); + moveEmplaceAll(init.data[0 .. init.length_], this.data[0 .. init.length_]); + this.length_ = init.length_; + // Destructor of init should destroy it here. + } + } + + /// + @trusted @nogc unittest + { + auto v1 = Array!int([1, 2, 3]); + auto v2 = Array!int(v1); + assert(v1 == v2); + + auto v3 = Array!int(Array!int([1, 2, 3])); + assert(v1 == v3); + assert(v3.length == 3); + assert(v3.capacity == 3); + } + + private @trusted @nogc unittest // const constructor tests + { + auto v1 = const Array!int([1, 2, 3]); + auto v2 = Array!int(v1); + assert(v1.data !is v2.data); + assert(v1 == v2); + + auto v3 = const Array!int(Array!int([1, 2, 3])); + assert(v1 == v3); + assert(v3.length == 3); + assert(v3.capacity == 3); + } + + /** + * Creates a new $(D_PSYMBOL Array). + * + * Params: + * len = Initial length of the array. + * init = Initial value to fill the array with. + * allocator = Allocator. + */ + this(const size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted + { + this(allocator); + reserve(len); + uninitializedFill(this.data[0 .. len], init); + length_ = len; + } + + /// Ditto. + this(const size_t len, shared Allocator allocator = defaultAllocator) + { + this(allocator); + length = len; + } + + /// Ditto. + this(shared Allocator allocator) + in + { + assert(allocator !is null); + } + body + { + allocator_ = allocator; + } + + /// + unittest + { + auto v = Array!int([3, 8, 2]); + + assert(v.capacity == 3); + assert(v.length == 3); + assert(v[0] == 3 && v[1] == 8 && v[2] == 2); + } + + /// + unittest + { + auto v = Array!int(3, 5); + + assert(v.capacity == 3); + assert(v.length == 3); + assert(v[0] == 5 && v[1] == 5 && v[2] == 5); + } + + @safe unittest + { + auto v1 = Array!int(defaultAllocator); + } + + /** + * Destroys this $(D_PSYMBOL Array). + */ + ~this() @trusted + { + clear(); + allocator.deallocate(this.data[0 .. capacity]); + } + + /** + * Copies the array. + */ + this(this) + { + auto buf = this.data[0 .. this.length_]; + this.length_ = capacity_ = 0; + this.data = null; + insertBack(buf); + } + + /** + * Removes all elements. + */ + void clear() + { + length = 0; + } + + /// + unittest + { + auto v = Array!int([18, 20, 15]); + v.clear(); + assert(v.length == 0); + assert(v.capacity == 3); + } + + /** + * Returns: How many elements the array can contain without reallocating. + */ + @property size_t capacity() const + { + return capacity_; + } + + /// + @safe @nogc unittest + { + auto v = Array!int(4); + assert(v.capacity == 4); + } + + /** + * Returns: Array length. + */ + @property size_t length() const + { + return length_; + } + + /// Ditto. + size_t opDollar() const + { + return length; + } + + /** + * Expands/shrinks the array. + * + * Params: + * len = New length. + */ + @property void length(const size_t len) @trusted + { + if (len == length) + { + return; + } + else if (len > length) + { + reserve(len); + initializeAll(this.data[length_ .. len]); + } + else + { + static if (hasElaborateDestructor!T) + { + const T* end = this.data + length_ - 1; + for (T* e = this.data + len; e != end; ++e) + { + destroy(*e); + } + } + } + length_ = len; + } + + /// + unittest + { + Array!int v; + + v.length = 5; + assert(v.length == 5); + assert(v.capacity == 5); + + v.length = 7; + assert(v.length == 7); + assert(v.capacity == 7); + + assert(v[$ - 1] == 0); + v[$ - 1] = 3; + assert(v[$ - 1] == 3); + + v.length = 0; + assert(v.length == 0); + assert(v.capacity == 7); + } + + /** + * Reserves space for $(D_PARAM size) elements. + * + * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the + * function call does not cause a reallocation and the array capacity is not + * affected. + * + * Params: + * size = Desired size. + */ + void reserve(const size_t size) @trusted + { + if (capacity_ >= size) + { + return; + } + bool overflow; + const byteSize = mulu(size, T.sizeof, overflow); + assert(!overflow); + + void[] buf = this.data[0 .. this.capacity_]; + if (!allocator.reallocateInPlace(buf, byteSize)) + { + buf = allocator.allocate(byteSize); + if (buf is null) + { + onOutOfMemoryErrorNoGC(); + } + scope (failure) + { + allocator.deallocate(buf); + } + const T* end = this.data + this.length_; + for (T* src = this.data, dest = cast(T*) buf; src != end; ++src, ++dest) + { + moveEmplace(*src, *dest); + static if (hasElaborateDestructor!T) + { + destroy(*src); + } + } + allocator.deallocate(this.data[0 .. this.capacity_]); + this.data = cast(T*) buf; + } + this.capacity_ = size; + } + + /// + @nogc @safe unittest + { + Array!int v; + assert(v.capacity == 0); + assert(v.length == 0); + + v.reserve(3); + assert(v.capacity == 3); + assert(v.length == 0); + } + + /** + * Requests the array to reduce its capacity to fit the $(D_PARAM size). + * + * The request is non-binding. The array won't become smaller than the + * $(D_PARAM length). + * + * Params: + * size = Desired size. + */ + void shrink(const size_t size) @trusted + { + if (capacity <= size) + { + return; + } + const n = max(length, size); + void[] buf = this.data[0 .. this.capacity_]; + if (allocator.reallocateInPlace(buf, n * T.sizeof)) + { + this.capacity_ = n; + } + } + + /// + @nogc @safe unittest + { + Array!int v; + assert(v.capacity == 0); + assert(v.length == 0); + + v.reserve(5); + v.insertBack(1); + v.insertBack(3); + assert(v.capacity == 5); + assert(v.length == 2); + } + + /** + * Returns: $(D_KEYWORD true) if the array is empty. + */ + @property bool empty() const + { + return length == 0; + } + + /** + * Removes the value at the back of the array. + * + * Returns: The number of elements removed + * + * Precondition: $(D_INLINECODE !empty). + */ + void removeBack() + in + { + assert(!empty); + } + body + { + length = length - 1; + } + + /** + * Removes $(D_PARAM howMany) elements from the array. + * + * This method doesn't fail if it could not remove $(D_PARAM howMany) + * elements. Instead, if $(D_PARAM howMany) is greater than the array + * length, all elements are removed. + * + * Params: + * howMany = How many elements should be removed. + * + * Returns: The number of elements removed + */ + size_t removeBack(const size_t howMany) + out (removed) + { + assert(removed <= howMany); + } + body + { + const toRemove = min(howMany, length); + + length = length - toRemove; + + return toRemove; + } + + /// + unittest + { + auto v = Array!int([5, 18, 17]); + + assert(v.removeBack(0) == 0); + assert(v.removeBack(2) == 2); + assert(v.removeBack(3) == 1); + assert(v.removeBack(3) == 0); + } + + /** + * Remove all elements beloning to $(D_PARAM r). + * + * Params: + * r = Range originally obtained from this array. + * + * Returns: A range spanning the remaining elements in the array that + * initially were right after $(D_PARAM r). + * + * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). + */ + Range!T remove(Range!T r) @trusted + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + auto end = this.data + this.length; + moveAll(Range!T(this, r.end, end), Range!T(this, r.begin, end)); + length = length - r.length; + return Range!T(this, r.begin, this.data + length); + } + + /// + @safe @nogc unittest + { + auto v = Array!int([5, 18, 17, 2, 4, 6, 1]); + + assert(v.remove(v[1 .. 3]).length == 4); + assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); + assert(v.length == 5); + + assert(v.remove(v[4 .. 4]).length == 1); + assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); + assert(v.length == 5); + + assert(v.remove(v[4 .. 5]).length == 0); + assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6); + assert(v.length == 4); + + assert(v.remove(v[]).length == 0); + + } + + private void moveBack(R)(ref R el) @trusted + if (isImplicitlyConvertible!(R, T)) + { + reserve(this.length + 1); + moveEmplace(el, *(this.data + this.length_)); + ++this.length_; + } + + /** + * Inserts the $(D_PARAM el) into the array. + * + * Params: + * R = Type of the inserted value(s) (single value, range or static array). + * el = Value(s) should be inserted. + * + * Returns: The number of elements inserted. + */ + size_t insertBack(R)(R el) + if (isImplicitlyConvertible!(R, T)) + { + moveBack(el); + return 1; + } + + /// Ditto. + size_t insertBack(R)(ref R el) @trusted + if (isImplicitlyConvertible!(R, T)) + { + reserve(this.length_ + 1); + emplace(this.data + this.length_, el); + ++this.length_; + return 1; + } + + /// Ditto. + size_t insertBack(R)(R el) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + static if (hasLength!R) + { + reserve(length + el.length); + } + size_t retLength; + foreach (e; el) + { + retLength += insertBack(e); + } + return retLength; + } + + /// Ditto. + size_t insertBack(size_t R)(T[R] el) + { + return insertBack!(T[])(el[]); + } + + /// Ditto. + alias insert = insertBack; + + /// + unittest + { + struct TestRange + { + int counter = 6; + + int front() + { + return counter; + } + + void popFront() + { + counter -= 2; + } + + bool empty() + { + return counter == 0; + } + } + + Array!int v1; + + assert(v1.insertBack(5) == 1); + assert(v1.length == 1); + assert(v1.capacity == 1); + assert(v1.back == 5); + + assert(v1.insertBack(TestRange()) == 3); + assert(v1.length == 4); + assert(v1.capacity == 4); + assert(v1[0] == 5 && v1[1] == 6 && v1[2] == 4 && v1[3] == 2); + + assert(v1.insertBack([34, 234]) == 2); + assert(v1.length == 6); + assert(v1.capacity == 6); + assert(v1[4] == 34 && v1[5] == 234); + } + + /** + * Inserts $(D_PARAM el) before or after $(D_PARAM r). + * + * Params: + * R = Type of the inserted value(s) (single value, range or static array). + * r = Range originally obtained from this array. + * el = Value(s) should be inserted. + * + * Returns: The number of elements inserted. + * + * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). + */ + size_t insertAfter(R)(Range!T r, R el) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + const oldLen = length; + const offset = r.end - this.data; + const inserted = insertBack(el); + bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); + return inserted; + } + + /// Ditto. + size_t insertAfter(size_t R)(Range!T r, T[R] el) + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + return insertAfter!(T[])(r, el[]); + } + + /// Ditto. + size_t insertAfter(R)(Range!T r, auto ref R el) + if (isImplicitlyConvertible!(R, T)) + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + const oldLen = length; + const offset = r.end - this.data; + + static if (__traits(isRef, el)) + { + insertBack(el); + } + else + { + moveBack(el); + } + bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); + + return 1; + } + + /// Ditto. + size_t insertBefore(R)(Range!T r, R el) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + return insertAfter(Range!T(this, this.data, r.begin), el); + } + + /// Ditto. + size_t insertBefore(size_t R)(Range!T r, T[R] el) + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + return insertBefore!(T[])(r, el[]); + } + + /// Ditto. + size_t insertBefore(R)(Range!T r, auto ref R el) + if (isImplicitlyConvertible!(R, T)) + in + { + assert(r.container is &this); + assert(r.begin >= this.data); + assert(r.end <= this.data + length); + } + body + { + const oldLen = length; + const offset = r.begin - this.data; + + static if (__traits(isRef, el)) + { + insertBack(el); + } + else + { + moveBack(el); + } + bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); + + return 1; + } + + /// + unittest + { + Array!int v1; + v1.insertAfter(v1[], [2, 8]); + assert(v1[0] == 2); + assert(v1[1] == 8); + assert(v1.length == 2); + + v1.insertAfter(v1[], [1, 2]); + assert(v1[0] == 2); + assert(v1[1] == 8); + assert(v1[2] == 1); + assert(v1[3] == 2); + assert(v1.length == 4); + + v1.insertAfter(v1[0 .. 0], [1, 2]); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 2); + assert(v1[3] == 8); + assert(v1[4] == 1); + assert(v1[5] == 2); + assert(v1.length == 6); + + v1.insertAfter(v1[0 .. 4], 9); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 2); + assert(v1[3] == 8); + assert(v1[4] == 9); + assert(v1[5] == 1); + assert(v1[6] == 2); + assert(v1.length == 7); + } + + /// + unittest + { + Array!int v1; + v1.insertBefore(v1[], [2, 8]); + assert(v1[0] == 2); + assert(v1[1] == 8); + assert(v1.length == 2); + + v1.insertBefore(v1[], [1, 2]); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 2); + assert(v1[3] == 8); + assert(v1.length == 4); + + v1.insertBefore(v1[0 .. 1], [1, 2]); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 1); + assert(v1[3] == 2); + assert(v1[4] == 2); + assert(v1[5] == 8); + assert(v1.length == 6); + + v1.insertBefore(v1[2 .. $], 9); + assert(v1[0] == 1); + assert(v1[1] == 2); + assert(v1[2] == 9); + assert(v1[3] == 1); + assert(v1[4] == 2); + assert(v1[5] == 2); + assert(v1[6] == 8); + assert(v1.length == 7); + } + + /** + * Assigns a value to the element with the index $(D_PARAM pos). + * + * Params: + * E = Value type. + * value = Value. + * pos = Position. + * + * Returns: Assigned value. + * + * Precondition: $(D_INLINECODE length > pos). + */ + ref T opIndexAssign(E : T)(auto ref E value, const size_t pos) + { + return opIndex(pos) = value; + } + + /// Ditto. + Range!T opIndexAssign(E : T)(auto ref E value) + { + return opSliceAssign(value, 0, length); + } + + /// + nothrow @safe @nogc unittest + { + Array!int a = Array!int(1); + a[0] = 5; + assert(a[0] == 5); + } + + /** + * Assigns a range or a static array. + * + * Params: + * R = Value type. + * value = Value. + * + * Returns: Assigned value. + * + * Precondition: $(D_INLINECODE length == value.length). + */ + Range!T opIndexAssign(size_t R)(T[R] value) + { + return opSliceAssign!R(value, 0, length); + } + + /// Ditto. + Range!T opIndexAssign(Range!T value) + { + return opSliceAssign(value, 0, length); + } + + /// + @nogc unittest + { + auto v1 = Array!int([12, 1, 7]); + + v1[] = 3; + assert(v1[0] == 3); + assert(v1[1] == 3); + assert(v1[2] == 3); + + v1[] = [7, 1, 12]; + assert(v1[0] == 7); + assert(v1[1] == 1); + assert(v1[2] == 12); + } + + /** + * Params: + * pos = Index. + * + * Returns: The value at a specified index. + * + * Precondition: $(D_INLINECODE length > pos). + */ + ref inout(T) opIndex(const size_t pos) inout @trusted + in + { + assert(length > pos); + } + body + { + return *(this.data + pos); + } + + /** + * Returns: Random access range that iterates over elements of the array, + * in forward order. + */ + Range!T opIndex() @trusted + { + return typeof(return)(this, this.data, this.data + length); + } + + /// Ditto. + Range!(const T) opIndex() const @trusted + { + return typeof(return)(this, this.data, this.data + length); + } + + /// + unittest + { + const v1 = Array!int([6, 123, 34, 5]); + + assert(v1[0] == 6); + assert(v1[1] == 123); + assert(v1[2] == 34); + assert(v1[3] == 5); + static assert(is(typeof(v1[0]) == const(int))); + static assert(is(typeof(v1[]))); + } + + /** + * Comparison for equality. + * + * Params: + * that = The array to compare with. + * + * Returns: $(D_KEYWORD true) if the arrays are equal, $(D_KEYWORD false) + * otherwise. + */ + bool opEquals()(auto ref typeof(this) that) @trusted + { + return equal(this.data[0 .. length], that.data[0 .. that.length]); + } + + /// Ditto. + bool opEquals()(const auto ref typeof(this) that) const @trusted + { + return equal(this.data[0 .. length], that.data[0 .. that.length]); + } + + /// Ditto. + bool opEquals(Range!T that) + { + return equal(opIndex(), that); + } + + /** + * Comparison for equality. + * + * Params: + * R = Right hand side type. + * that = Right hand side array range. + * + * Returns: $(D_KEYWORD true) if the array and the range are equal, + * $(D_KEYWORD false) otherwise. + */ + bool opEquals(R)(Range!R that) const + if (is(Unqual!R == T)) + { + return equal(opIndex(), that); + } + + /// + unittest + { + Array!int v1, v2; + assert(v1 == v2); + + v1.length = 1; + v2.length = 2; + assert(v1 != v2); + + v1.length = 2; + v1[0] = v2[0] = 2; + v1[1] = 3; + v2[1] = 4; + assert(v1 != v2); + + v2[1] = 3; + assert(v1 == v2); + } + + /** + * Returns: The first element. + * + * Precondition: $(D_INLINECODE !empty). + */ + @property ref inout(T) front() inout + in + { + assert(!empty); + } + body + { + return *this.data; + } + + /// + @safe unittest + { + auto v = Array!int([5]); + + assert(v.front == 5); + + v.length = 2; + v[1] = 15; + assert(v.front == 5); + } + + /** + * Returns: The last element. + * + * Precondition: $(D_INLINECODE !empty). + */ + @property ref inout(T) back() inout @trusted + in + { + assert(!empty); + } + body + { + return *(this.data + length - 1); + } + + /// + unittest + { + auto v = Array!int([5]); + + assert(v.back == 5); + + v.length = 2; + v[1] = 15; + assert(v.back == 15); + } + + /** + * Params: + * i = Slice start. + * j = Slice end. + * + * Returns: A range that iterates over elements of the container from + * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). + * + * Precondition: $(D_INLINECODE i <= j && j <= length). + */ + Range!T opSlice(const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, this.data + i, this.data + j); + } + + /// Ditto. + Range!(const T) opSlice(const size_t i, const size_t j) const @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + return typeof(return)(this, this.data + i, this.data + j); + } + + /// + unittest + { + Array!int v; + auto r = v[]; + assert(r.length == 0); + assert(r.empty); + } + + /// + unittest + { + auto v = Array!int([1, 2, 3]); + auto r = v[]; + + assert(r.front == 1); + assert(r.back == 3); + + r.popFront(); + assert(r.front == 2); + + r.popBack(); + assert(r.back == 2); + + assert(r.length == 1); + } + + /// + unittest + { + auto v = Array!int([1, 2, 3, 4]); + auto r = v[1 .. 4]; + assert(r.length == 3); + assert(r[0] == 2); + assert(r[1] == 3); + assert(r[2] == 4); + + r = v[0 .. 0]; + assert(r.length == 0); + + r = v[4 .. 4]; + assert(r.length == 0); + } + + /** + * Slicing assignment. + * + * Params: + * R = Type of the assigned slice or length of the static array should + * be assigned. + * value = New value (single value, range or static array). + * i = Slice start. + * j = Slice end. + * + * Returns: Slice with the assigned part of the array. + * + * Precondition: $(D_INLINECODE i <= j && j <= length + * && value.length == j - i) + */ + Range!T opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j) + @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + copy(value[], this.data[i .. j]); + return opSlice(i, j); + } + + /// Ditto. + Range!T opSliceAssign(R : T)(auto ref R value, const size_t i, const size_t j) + @trusted + in + { + assert(i <= j); + assert(j <= length); + } + body + { + fill(this.data[i .. j], value); + return opSlice(i, j); + } + + /// Ditto. + Range!T opSliceAssign(Range!T value, const size_t i, const size_t j) @trusted + in + { + assert(i <= j); + assert(j <= length); + assert(j - i == value.length); + } + body + { + copy(value, this.data[i .. j]); + return opSlice(i, j); + } + + /// + @nogc @safe unittest + { + auto v1 = Array!int([3, 3, 3]); + auto v2 = Array!int([1, 2]); + + v1[0 .. 2] = 286; + assert(v1[0] == 286); + assert(v1[1] == 286); + assert(v1[2] == 3); + + v2[0 .. $] = v1[1 .. 3]; + assert(v2[0] == 286); + assert(v2[1] == 3); + + v1[0 .. 2] = [5, 8]; + assert(v1[0] == 5); + assert(v1[1] == 8); + assert(v1[2] == 3); + } + + /** + * Returns an array used internally by the array to store its owned elements. + * The length of the returned array may differ from the size of the allocated + * memory for the array: the array contains only initialized elements, but + * not the reserved memory. + * + * Returns: The array with elements of this array. + */ + inout(T[]) get() inout @trusted + { + return this.data[0 .. length]; + } + + /// + unittest + { + auto v = Array!int([1, 2, 4]); + auto data = v.get(); + + assert(data[0] == 1); + assert(data[1] == 2); + assert(data[2] == 4); + assert(data.length == 3); + + data = v[1 .. 2].get(); + assert(data[0] == 2); + assert(data.length == 1); + } + + /** + * Assigns another array. + * + * If $(D_PARAM that) is passed by value, it won't be copied, but moved. + * This array will take the ownership over $(D_PARAM that)'s storage and + * the allocator. + * + * If $(D_PARAM that) is passed by reference, it will be copied. + * + * Params: + * R = Content type. + * that = The value should be assigned. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(R)(ref R that) + if (is(Unqual!R == Array)) + { + return this = that[]; + } + + /// Ditto. + ref typeof(this) opAssign(R)(R that) @trusted + if (is(R == Array)) + { + swap(this.data, that.data); + swap(this.length_, that.length_); + swap(this.capacity_, that.capacity_); + swap(this.allocator_, that.allocator_); + return this; + } + + /** + * Assigns a range to the array. + * + * Params: + * R = Content type. + * that = The value should be assigned. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(R)(R that) + if (!isInfinite!R + && isInputRange!R + && isImplicitlyConvertible!(ElementType!R, T)) + { + length = 0; + insertBack(that); + return this; + } + + /// + @safe @nogc unittest + { + auto v1 = const Array!int([5, 15, 8]); + Array!int v2; + v2 = v1; + assert(v1 == v2); + } + + /// + @safe @nogc unittest + { + auto v1 = const Array!int([5, 15, 8]); + Array!int v2; + v2 = v1[0 .. 2]; + assert(equal(v1[0 .. 2], v2[])); + } + + // Move assignment. + private @safe @nogc unittest + { + Array!int v1; + v1 = Array!int([5, 15, 8]); + } + + /** + * Assigns a static array. + * + * Params: + * R = Static array size. + * that = Values to initialize the array with. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(size_t R)(T[R] that) + { + return opAssign!(T[])(that[]); + } + + /// + @safe @nogc unittest + { + auto v1 = Array!int([5, 15, 8]); + Array!int v2; + + v2 = [5, 15, 8]; + assert(v1 == v2); + } + + mixin DefaultAllocator; +} + +/// +unittest +{ + auto v = Array!int([5, 15, 8]); + + assert(v.front == 5); + assert(v[1] == 15); + assert(v.back == 8); + + auto r = v[]; + r[0] = 7; + assert(r.front == 7); + assert(r.front == v.front); +} + +@nogc unittest +{ + const v1 = Array!int(); + const Array!int v2; + const v3 = Array!int([1, 5, 8]); + static assert(is(PointerTarget!(typeof(v3.data)) == const(int))); +} + +@nogc unittest +{ + // Test that const arrays return usable ranges. + auto v = const Array!int([1, 2, 4]); + auto r1 = v[]; + + assert(r1.back == 4); + r1.popBack(); + assert(r1.back == 2); + r1.popBack(); + assert(r1.back == 1); + r1.popBack(); + assert(r1.length == 0); + + static assert(!is(typeof(r1[0] = 5))); + static assert(!is(typeof(v[0] = 5))); + + const r2 = r1[]; + static assert(is(typeof(r2[]))); +} + +@nogc unittest +{ + Array!int v1; + const Array!int v2; + + auto r1 = v1[]; + auto r2 = v1[]; + + assert(r1.length == 0); + assert(r2.empty); + assert(r1 == r2); + + v1.insertBack([1, 2, 4]); + assert(v1[] == v1); + assert(v2[] == v2); + assert(v2[] != v1); + assert(v1[] != v2); + assert(v1[].equal(v1[])); + assert(v2[].equal(v2[])); + assert(!v1[].equal(v2[])); +} + +@nogc unittest +{ + struct MutableEqualsStruct + { + int opEquals(typeof(this) that) @nogc + { + return true; + } + } + struct ConstEqualsStruct + { + int opEquals(const typeof(this) that) const @nogc + { + return true; + } + } + auto v1 = Array!ConstEqualsStruct(); + auto v2 = Array!ConstEqualsStruct(); + assert(v1 == v2); + assert(v1[] == v2); + assert(v1 == v2[]); + assert(v1[].equal(v2[])); + + auto v3 = const Array!ConstEqualsStruct(); + auto v4 = const Array!ConstEqualsStruct(); + assert(v3 == v4); + assert(v3[] == v4); + assert(v3 == v4[]); + assert(v3[].equal(v4[])); + + auto v7 = Array!MutableEqualsStruct(1, MutableEqualsStruct()); + auto v8 = Array!MutableEqualsStruct(1, MutableEqualsStruct()); + assert(v7 == v8); + assert(v7[] == v8); + assert(v7 == v8[]); + assert(v7[].equal(v8[])); +} + +@nogc unittest +{ + struct SWithDtor + { + ~this() @nogc + { + } + } + auto v = Array!SWithDtor(); // Destructor can destroy empty arrays. +} + +private unittest +{ + class A + { + } + A a1, a2; + auto v1 = Array!A([a1, a2]); +} + +private @safe @nogc unittest +{ + auto v = Array!int([5, 15, 8]); + { + size_t i; + + foreach (e; v) + { + assert(i != 0 || e == 5); + assert(i != 1 || e == 15); + assert(i != 2 || e == 8); + ++i; + } + assert(i == 3); + } + { + size_t i = 3; + + foreach_reverse (e; v) + { + --i; + assert(i != 2 || e == 8); + assert(i != 1 || e == 15); + assert(i != 0 || e == 5); + } + assert(i == 0); + } +} diff --git a/source/tanya/container/package.d b/source/tanya/container/package.d index 35400d5..a8adf35 100644 --- a/source/tanya/container/package.d +++ b/source/tanya/container/package.d @@ -12,8 +12,8 @@ */ module tanya.container; +public import tanya.container.array; public import tanya.container.buffer; public import tanya.container.list; public import tanya.container.string; -public import tanya.container.vector; public import tanya.container.queue; diff --git a/source/tanya/container/vector.d b/source/tanya/container/vector.d index 7f5e3a5..a957fe5 100644 --- a/source/tanya/container/vector.d +++ b/source/tanya/container/vector.d @@ -10,1634 +10,7 @@ * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) */ +deprecated("Use tanya.container.array instead.") module tanya.container.vector; -import core.checkedint; -import core.exception; -import std.algorithm.comparison; -import std.algorithm.mutation; -import std.conv; -import std.range.primitives; -import std.meta; -import std.traits; -import tanya.memory; - -/** - * Random-access range for the $(D_PSYMBOL Vector). - * - * Params: - * E = Element type. - */ -struct Range(E) -{ - private E* begin, end; - private alias ContainerType = CopyConstness!(E, Vector!(Unqual!E)); - private ContainerType* container; - - invariant - { - assert(this.begin <= this.end); - assert(this.container !is null); - assert(this.begin >= this.container.data); - assert(this.end <= this.container.data + this.container.length); - } - - private this(ref ContainerType container, E* begin, E* end) @trusted - in - { - assert(begin <= end); - assert(begin >= container.data); - assert(end <= container.data + container.length); - } - body - { - this.container = &container; - this.begin = begin; - this.end = end; - } - - @disable this(); - - @property Range save() - { - return this; - } - - @property bool empty() const - { - return this.begin == this.end; - } - - @property size_t length() const - { - return this.end - this.begin; - } - - alias opDollar = length; - - @property ref inout(E) front() inout - in - { - assert(!empty); - } - body - { - return *this.begin; - } - - @property ref inout(E) back() inout @trusted - in - { - assert(!empty); - } - body - { - return *(this.end - 1); - } - - void popFront() @trusted - in - { - assert(!empty); - } - body - { - ++this.begin; - } - - void popBack() @trusted - in - { - assert(!empty); - } - body - { - --this.end; - } - - ref inout(E) opIndex(const size_t i) inout @trusted - in - { - assert(i < length); - } - body - { - return *(this.begin + i); - } - - Range opIndex() - { - return typeof(return)(*this.container, this.begin, this.end); - } - - Range!(const E) opIndex() const - { - return typeof(return)(*this.container, this.begin, this.end); - } - - Range opSlice(const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(*this.container, this.begin + i, this.begin + j); - } - - Range!(const E) opSlice(const size_t i, const size_t j) const @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(*this.container, this.begin + i, this.begin + j); - } - - inout(E[]) get() inout @trusted - { - return this.begin[0 .. length]; - } -} - -/** - * One dimensional array. - * - * Params: - * T = Content type. - */ -struct Vector(T) -{ - private size_t length_; - private T* data; - private size_t capacity_; - - invariant - { - assert(this.length_ <= this.capacity_); - assert(this.capacity_ == 0 || this.data !is null); - } - - /** - * Creates a new $(D_PSYMBOL Vector) with the elements from a static array. - * - * Params: - * R = Static array size. - * init = Values to initialize the vector with. - * allocator = Allocator. - */ - this(size_t R)(T[R] init, shared Allocator allocator = defaultAllocator) - { - this(allocator); - insertBack!(T[])(init[]); - } - - /** - * Creates a new $(D_PSYMBOL Vector) with the elements from an input range. - * - * Params: - * R = Type of the initial range. - * init = Values to initialize the vector with. - * allocator = Allocator. - */ - this(R)(R init, shared Allocator allocator = defaultAllocator) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - this(allocator); - insertBack(init); - } - - /** - * Initializes this vector from another one. - * - * If $(D_PARAM init) is passed by value, it won't be copied, but moved. - * If the allocator of ($D_PARAM init) matches $(D_PARAM allocator), - * $(D_KEYWORD this) will just take the ownership over $(D_PARAM init)'s - * storage, otherwise, the storage will be allocated with - * $(D_PARAM allocator) and all elements will be moved; - * $(D_PARAM init) will be destroyed at the end. - * - * If $(D_PARAM init) is passed by reference, it will be copied. - * - * Params: - * R = Source vector type. - * init = Source vector. - * allocator = Allocator. - */ - this(R)(ref R init, shared Allocator allocator = defaultAllocator) - if (is(Unqual!R == Vector)) - { - this(allocator); - insertBack(init[]); - } - - /// Ditto. - this(R)(R init, shared Allocator allocator = defaultAllocator) @trusted - if (is(R == Vector)) - { - this(allocator); - if (allocator is init.allocator) - { - // Just steal all references and the allocator. - this.data = init.data; - this.length_ = init.length_; - this.capacity_ = init.capacity_; - - // Reset the source vector, so it can't destroy the moved storage. - init.length_ = init.capacity_ = 0; - init.data = null; - } - else - { - // Move each element. - reserve(init.length_); - moveEmplaceAll(init.data[0 .. init.length_], this.data[0 .. init.length_]); - this.length_ = init.length_; - // Destructor of init should destroy it here. - } - } - - /// - @trusted @nogc unittest - { - auto v1 = Vector!int([1, 2, 3]); - auto v2 = Vector!int(v1); - assert(v1 == v2); - - auto v3 = Vector!int(Vector!int([1, 2, 3])); - assert(v1 == v3); - assert(v3.length == 3); - assert(v3.capacity == 3); - } - - private @trusted @nogc unittest // const constructor tests - { - auto v1 = const Vector!int([1, 2, 3]); - auto v2 = Vector!int(v1); - assert(v1.data !is v2.data); - assert(v1 == v2); - - auto v3 = const Vector!int(Vector!int([1, 2, 3])); - assert(v1 == v3); - assert(v3.length == 3); - assert(v3.capacity == 3); - } - - /** - * Creates a new $(D_PSYMBOL Vector). - * - * Params: - * len = Initial length of the vector. - * init = Initial value to fill the vector with. - * allocator = Allocator. - */ - this(const size_t len, T init, shared Allocator allocator = defaultAllocator) @trusted - { - this(allocator); - reserve(len); - uninitializedFill(this.data[0 .. len], init); - length_ = len; - } - - /// Ditto. - this(const size_t len, shared Allocator allocator = defaultAllocator) - { - this(allocator); - length = len; - } - - /// Ditto. - this(shared Allocator allocator) - in - { - assert(allocator !is null); - } - body - { - allocator_ = allocator; - } - - /// - unittest - { - auto v = Vector!int([3, 8, 2]); - - assert(v.capacity == 3); - assert(v.length == 3); - assert(v[0] == 3 && v[1] == 8 && v[2] == 2); - } - - /// - unittest - { - auto v = Vector!int(3, 5); - - assert(v.capacity == 3); - assert(v.length == 3); - assert(v[0] == 5 && v[1] == 5 && v[2] == 5); - } - - @safe unittest - { - auto v1 = Vector!int(defaultAllocator); - } - - /** - * Destroys this $(D_PSYMBOL Vector). - */ - ~this() @trusted - { - clear(); - allocator.deallocate(this.data[0 .. capacity]); - } - - /** - * Copies the vector. - */ - this(this) - { - auto buf = this.data[0 .. this.length_]; - this.length_ = capacity_ = 0; - this.data = null; - insertBack(buf); - } - - /** - * Removes all elements. - */ - void clear() - { - length = 0; - } - - /// - unittest - { - auto v = Vector!int([18, 20, 15]); - v.clear(); - assert(v.length == 0); - assert(v.capacity == 3); - } - - /** - * Returns: How many elements the vector can contain without reallocating. - */ - @property size_t capacity() const - { - return capacity_; - } - - /// - @safe @nogc unittest - { - auto v = Vector!int(4); - assert(v.capacity == 4); - } - - /** - * Returns: Vector length. - */ - @property size_t length() const - { - return length_; - } - - /// Ditto. - size_t opDollar() const - { - return length; - } - - /** - * Expands/shrinks the vector. - * - * Params: - * len = New length. - */ - @property void length(const size_t len) @trusted - { - if (len == length) - { - return; - } - else if (len > length) - { - reserve(len); - initializeAll(this.data[length_ .. len]); - } - else - { - static if (hasElaborateDestructor!T) - { - const T* end = this.data + length_ - 1; - for (T* e = this.data + len; e != end; ++e) - { - destroy(*e); - } - } - } - length_ = len; - } - - /// - unittest - { - Vector!int v; - - v.length = 5; - assert(v.length == 5); - assert(v.capacity == 5); - - v.length = 7; - assert(v.length == 7); - assert(v.capacity == 7); - - assert(v[$ - 1] == 0); - v[$ - 1] = 3; - assert(v[$ - 1] == 3); - - v.length = 0; - assert(v.length == 0); - assert(v.capacity == 7); - } - - /** - * Reserves space for $(D_PARAM size) elements. - * - * If $(D_PARAM size) is less than or equal to the $(D_PSYMBOL capacity), the - * function call does not cause a reallocation and the vector capacity is not - * affected. - * - * Params: - * size = Desired size. - */ - void reserve(const size_t size) @trusted - { - if (capacity_ >= size) - { - return; - } - bool overflow; - const byteSize = mulu(size, T.sizeof, overflow); - assert(!overflow); - - void[] buf = this.data[0 .. this.capacity_]; - if (!allocator.reallocateInPlace(buf, byteSize)) - { - buf = allocator.allocate(byteSize); - if (buf is null) - { - onOutOfMemoryErrorNoGC(); - } - scope (failure) - { - allocator.deallocate(buf); - } - const T* end = this.data + this.length_; - for (T* src = this.data, dest = cast(T*) buf; src != end; ++src, ++dest) - { - moveEmplace(*src, *dest); - static if (hasElaborateDestructor!T) - { - destroy(*src); - } - } - allocator.deallocate(this.data[0 .. this.capacity_]); - this.data = cast(T*) buf; - } - this.capacity_ = size; - } - - /// - @nogc @safe unittest - { - Vector!int v; - assert(v.capacity == 0); - assert(v.length == 0); - - v.reserve(3); - assert(v.capacity == 3); - assert(v.length == 0); - } - - /** - * Requests the vector to reduce its capacity to fit the $(D_PARAM size). - * - * The request is non-binding. The vector won't become smaller than the - * $(D_PARAM length). - * - * Params: - * size = Desired size. - */ - void shrink(const size_t size) @trusted - { - if (capacity <= size) - { - return; - } - const n = max(length, size); - void[] buf = this.data[0 .. this.capacity_]; - if (allocator.reallocateInPlace(buf, n * T.sizeof)) - { - this.capacity_ = n; - } - } - - /// - @nogc @safe unittest - { - Vector!int v; - assert(v.capacity == 0); - assert(v.length == 0); - - v.reserve(5); - v.insertBack(1); - v.insertBack(3); - assert(v.capacity == 5); - assert(v.length == 2); - } - - /** - * Returns: $(D_KEYWORD true) if the vector is empty. - */ - @property bool empty() const - { - return length == 0; - } - - /** - * Removes the value at the back of the vector. - * - * Returns: The number of elements removed - * - * Precondition: $(D_INLINECODE !empty). - */ - void removeBack() - in - { - assert(!empty); - } - body - { - length = length - 1; - } - - /** - * Removes $(D_PARAM howMany) elements from the vector. - * - * This method doesn't fail if it could not remove $(D_PARAM howMany) - * elements. Instead, if $(D_PARAM howMany) is greater than the vector - * length, all elements are removed. - * - * Params: - * howMany = How many elements should be removed. - * - * Returns: The number of elements removed - */ - size_t removeBack(const size_t howMany) - out (removed) - { - assert(removed <= howMany); - } - body - { - const toRemove = min(howMany, length); - - length = length - toRemove; - - return toRemove; - } - - /// - unittest - { - auto v = Vector!int([5, 18, 17]); - - assert(v.removeBack(0) == 0); - assert(v.removeBack(2) == 2); - assert(v.removeBack(3) == 1); - assert(v.removeBack(3) == 0); - } - - /** - * Remove all elements beloning to $(D_PARAM r). - * - * Params: - * r = Range originally obtained from this vector. - * - * Returns: A range spanning the remaining elements in the array that - * initially were right after $(D_PARAM r). - * - * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). - */ - Range!T remove(Range!T r) @trusted - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - auto end = this.data + this.length; - moveAll(Range!T(this, r.end, end), Range!T(this, r.begin, end)); - length = length - r.length; - return Range!T(this, r.begin, this.data + length); - } - - /// - @safe @nogc unittest - { - auto v = Vector!int([5, 18, 17, 2, 4, 6, 1]); - - assert(v.remove(v[1 .. 3]).length == 4); - assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); - assert(v.length == 5); - - assert(v.remove(v[4 .. 4]).length == 1); - assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6 && v[4] == 1); - assert(v.length == 5); - - assert(v.remove(v[4 .. 5]).length == 0); - assert(v[0] == 5 && v[1] == 2 && v[2] == 4 && v[3] == 6); - assert(v.length == 4); - - assert(v.remove(v[]).length == 0); - - } - - private void moveBack(R)(ref R el) @trusted - if (isImplicitlyConvertible!(R, T)) - { - reserve(this.length + 1); - moveEmplace(el, *(this.data + this.length_)); - ++this.length_; - } - - /** - * Inserts the $(D_PARAM el) into the vector. - * - * Params: - * R = Type of the inserted value(s) (single value, range or static array). - * el = Value(s) should be inserted. - * - * Returns: The number of elements inserted. - */ - size_t insertBack(R)(R el) - if (isImplicitlyConvertible!(R, T)) - { - moveBack(el); - return 1; - } - - /// Ditto. - size_t insertBack(R)(ref R el) @trusted - if (isImplicitlyConvertible!(R, T)) - { - reserve(this.length_ + 1); - emplace(this.data + this.length_, el); - ++this.length_; - return 1; - } - - /// Ditto. - size_t insertBack(R)(R el) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - static if (hasLength!R) - { - reserve(length + el.length); - } - size_t retLength; - foreach (e; el) - { - retLength += insertBack(e); - } - return retLength; - } - - /// Ditto. - size_t insertBack(size_t R)(T[R] el) - { - return insertBack!(T[])(el[]); - } - - /// Ditto. - alias insert = insertBack; - - /// - unittest - { - struct TestRange - { - int counter = 6; - - int front() - { - return counter; - } - - void popFront() - { - counter -= 2; - } - - bool empty() - { - return counter == 0; - } - } - - Vector!int v1; - - assert(v1.insertBack(5) == 1); - assert(v1.length == 1); - assert(v1.capacity == 1); - assert(v1.back == 5); - - assert(v1.insertBack(TestRange()) == 3); - assert(v1.length == 4); - assert(v1.capacity == 4); - assert(v1[0] == 5 && v1[1] == 6 && v1[2] == 4 && v1[3] == 2); - - assert(v1.insertBack([34, 234]) == 2); - assert(v1.length == 6); - assert(v1.capacity == 6); - assert(v1[4] == 34 && v1[5] == 234); - } - - /** - * Inserts $(D_PARAM el) before or after $(D_PARAM r). - * - * Params: - * R = Type of the inserted value(s) (single value, range or static array). - * r = Range originally obtained from this vector. - * el = Value(s) should be inserted. - * - * Returns: The number of elements inserted. - * - * Precondition: $(D_PARAM r) refers to a region of $(D_KEYWORD this). - */ - size_t insertAfter(R)(Range!T r, R el) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - const oldLen = length; - const offset = r.end - this.data; - const inserted = insertBack(el); - bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); - return inserted; - } - - /// Ditto. - size_t insertAfter(size_t R)(Range!T r, T[R] el) - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - return insertAfter!(T[])(r, el[]); - } - - /// Ditto. - size_t insertAfter(R)(Range!T r, auto ref R el) - if (isImplicitlyConvertible!(R, T)) - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - const oldLen = length; - const offset = r.end - this.data; - - static if (__traits(isRef, el)) - { - insertBack(el); - } - else - { - moveBack(el); - } - bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); - - return 1; - } - - /// Ditto. - size_t insertBefore(R)(Range!T r, R el) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - return insertAfter(Range!T(this, this.data, r.begin), el); - } - - /// Ditto. - size_t insertBefore(size_t R)(Range!T r, T[R] el) - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - return insertBefore!(T[])(r, el[]); - } - - /// Ditto. - size_t insertBefore(R)(Range!T r, auto ref R el) - if (isImplicitlyConvertible!(R, T)) - in - { - assert(r.container is &this); - assert(r.begin >= this.data); - assert(r.end <= this.data + length); - } - body - { - const oldLen = length; - const offset = r.begin - this.data; - - static if (__traits(isRef, el)) - { - insertBack(el); - } - else - { - moveBack(el); - } - bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]); - - return 1; - } - - /// - unittest - { - Vector!int v1; - v1.insertAfter(v1[], [2, 8]); - assert(v1[0] == 2); - assert(v1[1] == 8); - assert(v1.length == 2); - - v1.insertAfter(v1[], [1, 2]); - assert(v1[0] == 2); - assert(v1[1] == 8); - assert(v1[2] == 1); - assert(v1[3] == 2); - assert(v1.length == 4); - - v1.insertAfter(v1[0 .. 0], [1, 2]); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 2); - assert(v1[3] == 8); - assert(v1[4] == 1); - assert(v1[5] == 2); - assert(v1.length == 6); - - v1.insertAfter(v1[0 .. 4], 9); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 2); - assert(v1[3] == 8); - assert(v1[4] == 9); - assert(v1[5] == 1); - assert(v1[6] == 2); - assert(v1.length == 7); - } - - /// - unittest - { - Vector!int v1; - v1.insertBefore(v1[], [2, 8]); - assert(v1[0] == 2); - assert(v1[1] == 8); - assert(v1.length == 2); - - v1.insertBefore(v1[], [1, 2]); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 2); - assert(v1[3] == 8); - assert(v1.length == 4); - - v1.insertBefore(v1[0 .. 1], [1, 2]); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 1); - assert(v1[3] == 2); - assert(v1[4] == 2); - assert(v1[5] == 8); - assert(v1.length == 6); - - v1.insertBefore(v1[2 .. $], 9); - assert(v1[0] == 1); - assert(v1[1] == 2); - assert(v1[2] == 9); - assert(v1[3] == 1); - assert(v1[4] == 2); - assert(v1[5] == 2); - assert(v1[6] == 8); - assert(v1.length == 7); - } - - /** - * Assigns a value to the element with the index $(D_PARAM pos). - * - * Params: - * E = Value type. - * value = Value. - * pos = Position. - * - * Returns: Assigned value. - * - * Precondition: $(D_INLINECODE length > pos). - */ - ref T opIndexAssign(E : T)(auto ref E value, const size_t pos) - { - return opIndex(pos) = value; - } - - /// Ditto. - Range!T opIndexAssign(E : T)(auto ref E value) - { - return opSliceAssign(value, 0, length); - } - - /// - nothrow @safe @nogc unittest - { - Vector!int a = Vector!int(1); - a[0] = 5; - assert(a[0] == 5); - } - - /** - * Assigns a range or a static array. - * - * Params: - * R = Value type. - * value = Value. - * - * Returns: Assigned value. - * - * Precondition: $(D_INLINECODE length == value.length). - */ - Range!T opIndexAssign(size_t R)(T[R] value) - { - return opSliceAssign!R(value, 0, length); - } - - /// Ditto. - Range!T opIndexAssign(Range!T value) - { - return opSliceAssign(value, 0, length); - } - - /// - @nogc unittest - { - auto v1 = Vector!int([12, 1, 7]); - - v1[] = 3; - assert(v1[0] == 3); - assert(v1[1] == 3); - assert(v1[2] == 3); - - v1[] = [7, 1, 12]; - assert(v1[0] == 7); - assert(v1[1] == 1); - assert(v1[2] == 12); - } - - /** - * Params: - * pos = Index. - * - * Returns: The value at a specified index. - * - * Precondition: $(D_INLINECODE length > pos). - */ - ref inout(T) opIndex(const size_t pos) inout @trusted - in - { - assert(length > pos); - } - body - { - return *(this.data + pos); - } - - /** - * Returns: Random access range that iterates over elements of the vector, in - * forward order. - */ - Range!T opIndex() @trusted - { - return typeof(return)(this, this.data, this.data + length); - } - - /// Ditto. - Range!(const T) opIndex() const @trusted - { - return typeof(return)(this, this.data, this.data + length); - } - - /// - unittest - { - const v1 = Vector!int([6, 123, 34, 5]); - - assert(v1[0] == 6); - assert(v1[1] == 123); - assert(v1[2] == 34); - assert(v1[3] == 5); - static assert(is(typeof(v1[0]) == const(int))); - static assert(is(typeof(v1[]))); - } - - /** - * Comparison for equality. - * - * Params: - * that = The vector to compare with. - * - * Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false) - * otherwise. - */ - bool opEquals()(auto ref typeof(this) that) @trusted - { - return equal(this.data[0 .. length], that.data[0 .. that.length]); - } - - /// Ditto. - bool opEquals()(const auto ref typeof(this) that) const @trusted - { - return equal(this.data[0 .. length], that.data[0 .. that.length]); - } - - /// Ditto. - bool opEquals(Range!T that) - { - return equal(opIndex(), that); - } - - /** - * Comparison for equality. - * - * Params: - * R = Right hand side type. - * that = Right hand side vector range. - * - * Returns: $(D_KEYWORD true) if the vector and the range are equal, - * $(D_KEYWORD false) otherwise. - */ - bool opEquals(R)(Range!R that) const - if (is(Unqual!R == T)) - { - return equal(opIndex(), that); - } - - /// - unittest - { - Vector!int v1, v2; - assert(v1 == v2); - - v1.length = 1; - v2.length = 2; - assert(v1 != v2); - - v1.length = 2; - v1[0] = v2[0] = 2; - v1[1] = 3; - v2[1] = 4; - assert(v1 != v2); - - v2[1] = 3; - assert(v1 == v2); - } - - /** - * Returns: The first element. - * - * Precondition: $(D_INLINECODE !empty). - */ - @property ref inout(T) front() inout - in - { - assert(!empty); - } - body - { - return *this.data; - } - - /// - @safe unittest - { - auto v = Vector!int([5]); - - assert(v.front == 5); - - v.length = 2; - v[1] = 15; - assert(v.front == 5); - } - - /** - * Returns: The last element. - * - * Precondition: $(D_INLINECODE !empty). - */ - @property ref inout(T) back() inout @trusted - in - { - assert(!empty); - } - body - { - return *(this.data + length - 1); - } - - /// - unittest - { - auto v = Vector!int([5]); - - assert(v.back == 5); - - v.length = 2; - v[1] = 15; - assert(v.back == 15); - } - - /** - * Params: - * i = Slice start. - * j = Slice end. - * - * Returns: A range that iterates over elements of the container from - * index $(D_PARAM i) up to (excluding) index $(D_PARAM j). - * - * Precondition: $(D_INLINECODE i <= j && j <= length). - */ - Range!T opSlice(const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(this, this.data + i, this.data + j); - } - - /// Ditto. - Range!(const T) opSlice(const size_t i, const size_t j) const @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - return typeof(return)(this, this.data + i, this.data + j); - } - - /// - unittest - { - Vector!int v; - auto r = v[]; - assert(r.length == 0); - assert(r.empty); - } - - /// - unittest - { - auto v = Vector!int([1, 2, 3]); - auto r = v[]; - - assert(r.front == 1); - assert(r.back == 3); - - r.popFront(); - assert(r.front == 2); - - r.popBack(); - assert(r.back == 2); - - assert(r.length == 1); - } - - /// - unittest - { - auto v = Vector!int([1, 2, 3, 4]); - auto r = v[1 .. 4]; - assert(r.length == 3); - assert(r[0] == 2); - assert(r[1] == 3); - assert(r[2] == 4); - - r = v[0 .. 0]; - assert(r.length == 0); - - r = v[4 .. 4]; - assert(r.length == 0); - } - - /** - * Slicing assignment. - * - * Params: - * R = Type of the assigned slice or length of the static array should - * be assigned. - * value = New value (single value, range or static array). - * i = Slice start. - * j = Slice end. - * - * Returns: Slice with the assigned part of the vector. - * - * Precondition: $(D_INLINECODE i <= j && j <= length - * && value.length == j - i) - */ - Range!T opSliceAssign(size_t R)(T[R] value, const size_t i, const size_t j) - @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - copy(value[], this.data[i .. j]); - return opSlice(i, j); - } - - /// Ditto. - Range!T opSliceAssign(R : T)(auto ref R value, const size_t i, const size_t j) - @trusted - in - { - assert(i <= j); - assert(j <= length); - } - body - { - fill(this.data[i .. j], value); - return opSlice(i, j); - } - - /// Ditto. - Range!T opSliceAssign(Range!T value, const size_t i, const size_t j) @trusted - in - { - assert(i <= j); - assert(j <= length); - assert(j - i == value.length); - } - body - { - copy(value, this.data[i .. j]); - return opSlice(i, j); - } - - /// - @nogc @safe unittest - { - auto v1 = Vector!int([3, 3, 3]); - auto v2 = Vector!int([1, 2]); - - v1[0 .. 2] = 286; - assert(v1[0] == 286); - assert(v1[1] == 286); - assert(v1[2] == 3); - - v2[0 .. $] = v1[1 .. 3]; - assert(v2[0] == 286); - assert(v2[1] == 3); - - v1[0 .. 2] = [5, 8]; - assert(v1[0] == 5); - assert(v1[1] == 8); - assert(v1[2] == 3); - } - - /** - * Returns an array used internally by the vector to store its owned elements. - * The length of the returned array may differ from the size of the allocated - * memory for the vector: the array contains only initialized elements, but - * not the reserved memory. - * - * Returns: The array with elements of this vector. - */ - inout(T[]) get() inout @trusted - { - return this.data[0 .. length]; - } - - /// - unittest - { - auto v = Vector!int([1, 2, 4]); - auto data = v.get(); - - assert(data[0] == 1); - assert(data[1] == 2); - assert(data[2] == 4); - assert(data.length == 3); - - data = v[1 .. 2].get(); - assert(data[0] == 2); - assert(data.length == 1); - } - - /** - * Assigns another vector. - * - * If $(D_PARAM that) is passed by value, it won't be copied, but moved. - * This vector will take the ownership over $(D_PARAM that)'s storage and - * the allocator. - * - * If $(D_PARAM that) is passed by reference, it will be copied. - * - * Params: - * R = Content type. - * that = The value should be assigned. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(R)(ref R that) - if (is(Unqual!R == Vector)) - { - return this = that[]; - } - - /// Ditto. - ref typeof(this) opAssign(R)(R that) @trusted - if (is(R == Vector)) - { - swap(this.data, that.data); - swap(this.length_, that.length_); - swap(this.capacity_, that.capacity_); - swap(this.allocator_, that.allocator_); - return this; - } - - /** - * Assigns a range to the vector. - * - * Params: - * R = Content type. - * that = The value should be assigned. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(R)(R that) - if (!isInfinite!R - && isInputRange!R - && isImplicitlyConvertible!(ElementType!R, T)) - { - length = 0; - insertBack(that); - return this; - } - - /// - @safe @nogc unittest - { - auto v1 = const Vector!int([5, 15, 8]); - Vector!int v2; - v2 = v1; - assert(v1 == v2); - } - - /// - @safe @nogc unittest - { - auto v1 = const Vector!int([5, 15, 8]); - Vector!int v2; - v2 = v1[0 .. 2]; - assert(equal(v1[0 .. 2], v2[])); - } - - // Move assignment. - private @safe @nogc unittest - { - Vector!int v1; - v1 = Vector!int([5, 15, 8]); - } - - /** - * Assigns a static array. - * - * Params: - * R = Static array size. - * that = Values to initialize the vector with. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(size_t R)(T[R] that) - { - return opAssign!(T[])(that[]); - } - - /// - @safe @nogc unittest - { - auto v1 = Vector!int([5, 15, 8]); - Vector!int v2; - - v2 = [5, 15, 8]; - assert(v1 == v2); - } - - mixin DefaultAllocator; -} - -/// -unittest -{ - auto v = Vector!int([5, 15, 8]); - - assert(v.front == 5); - assert(v[1] == 15); - assert(v.back == 8); - - auto r = v[]; - r[0] = 7; - assert(r.front == 7); - assert(r.front == v.front); -} - -@nogc unittest -{ - const v1 = Vector!int(); - const Vector!int v2; - const v3 = Vector!int([1, 5, 8]); - static assert(is(PointerTarget!(typeof(v3.data)) == const(int))); -} - -@nogc unittest -{ - // Test that const vectors return usable ranges. - auto v = const Vector!int([1, 2, 4]); - auto r1 = v[]; - - assert(r1.back == 4); - r1.popBack(); - assert(r1.back == 2); - r1.popBack(); - assert(r1.back == 1); - r1.popBack(); - assert(r1.length == 0); - - static assert(!is(typeof(r1[0] = 5))); - static assert(!is(typeof(v[0] = 5))); - - const r2 = r1[]; - static assert(is(typeof(r2[]))); -} - -@nogc unittest -{ - Vector!int v1; - const Vector!int v2; - - auto r1 = v1[]; - auto r2 = v1[]; - - assert(r1.length == 0); - assert(r2.empty); - assert(r1 == r2); - - v1.insertBack([1, 2, 4]); - assert(v1[] == v1); - assert(v2[] == v2); - assert(v2[] != v1); - assert(v1[] != v2); - assert(v1[].equal(v1[])); - assert(v2[].equal(v2[])); - assert(!v1[].equal(v2[])); -} - -@nogc unittest -{ - struct MutableEqualsStruct - { - int opEquals(typeof(this) that) @nogc - { - return true; - } - } - struct ConstEqualsStruct - { - int opEquals(const typeof(this) that) const @nogc - { - return true; - } - } - auto v1 = Vector!ConstEqualsStruct(); - auto v2 = Vector!ConstEqualsStruct(); - assert(v1 == v2); - assert(v1[] == v2); - assert(v1 == v2[]); - assert(v1[].equal(v2[])); - - auto v3 = const Vector!ConstEqualsStruct(); - auto v4 = const Vector!ConstEqualsStruct(); - assert(v3 == v4); - assert(v3[] == v4); - assert(v3 == v4[]); - assert(v3[].equal(v4[])); - - auto v7 = Vector!MutableEqualsStruct(1, MutableEqualsStruct()); - auto v8 = Vector!MutableEqualsStruct(1, MutableEqualsStruct()); - assert(v7 == v8); - assert(v7[] == v8); - assert(v7 == v8[]); - assert(v7[].equal(v8[])); -} - -@nogc unittest -{ - struct SWithDtor - { - ~this() @nogc - { - } - } - auto v = Vector!SWithDtor(); // Destructor can destroy empty vectors. -} - -private unittest -{ - class A - { - } - A a1, a2; - auto v1 = Vector!A([a1, a2]); -} - -private @safe @nogc unittest -{ - auto v = Vector!int([5, 15, 8]); - { - size_t i; - - foreach (e; v) - { - assert(i != 0 || e == 5); - assert(i != 1 || e == 15); - assert(i != 2 || e == 8); - ++i; - } - assert(i == 3); - } - { - size_t i = 3; - - foreach_reverse (e; v) - { - --i; - assert(i != 2 || e == 8); - assert(i != 1 || e == 15); - assert(i != 0 || e == 5); - } - assert(i == 0); - } -} +public import tanya.container.array; diff --git a/source/tanya/math/mp.d b/source/tanya/math/mp.d index 057a10d..d22b8be 100644 --- a/source/tanya/math/mp.d +++ b/source/tanya/math/mp.d @@ -16,7 +16,7 @@ import std.algorithm; import std.ascii; import std.range; import std.traits; -import tanya.container.vector; +import tanya.container.array; import tanya.memory; /** @@ -1444,26 +1444,26 @@ struct Integer /** * Returns: Two's complement representation of the integer. */ - Vector!ubyte toVector() const nothrow @safe @nogc - out (vector) + Array!ubyte toArray() const nothrow @safe @nogc + out (array) { - assert(vector.length == length); + assert(array.length == length); } body { - Vector!ubyte vector; + Array!ubyte array; if (this.size == 0) { - return vector; + return array; } const bc = countBits(); const remainingBits = bc & 0x07; - vector.reserve(bc / 8); + array.reserve(bc / 8); if (remainingBits == 0) { - vector.insertBack(ubyte.init); + array.insertBack(ubyte.init); } @@ -1486,14 +1486,14 @@ struct Integer do { - vector.insertBack(cast(ubyte) (tmp.rep[0] & 0xff)); + array.insertBack(cast(ubyte) (tmp.rep[0] & 0xff)); tmp >>= 8; } while (tmp != 0); - vector[].reverse(); + array[].reverse(); - return vector; + return array; } /// @@ -1503,15 +1503,15 @@ struct Integer auto integer = Integer(0x66778899aabbddee); ubyte[8] expected = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ]; - auto vector = integer.toVector(); - assert(equal(vector[], expected[])); + auto array = integer.toArray(); + assert(equal(array[], expected[])); } { auto integer = Integer(0x03); ubyte[1] expected = [ 0x03 ]; - auto vector = integer.toVector(); - assert(equal(vector[], expected[])); + auto array = integer.toArray(); + assert(equal(array[], expected[])); } { ubyte[63] expected = [ @@ -1526,8 +1526,8 @@ struct Integer ]; auto integer = Integer(Sign.positive, expected[]); - auto vector = integer.toVector(); - assert(equal(vector[], expected[])); + auto array = integer.toArray(); + assert(equal(array[], expected[])); } { ubyte[14] expected = [ @@ -1536,8 +1536,8 @@ struct Integer ]; auto integer = Integer(Sign.positive, expected[]); - auto vector = integer.toVector(); - assert(equal(vector[], expected[])); + auto array = integer.toArray(); + assert(equal(array[], expected[])); } }