Add code
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| .dub | ||||
| __test__*__ | ||||
							
								
								
									
										17
									
								
								dub.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								dub.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| { | ||||
| 	"name": "tanya", | ||||
| 	"description": "D library with event loop", | ||||
| 	"license": "MPL-2.0", | ||||
| 	"copyright": "(c) Eugene Wissner <info@caraus.de>", | ||||
| 	"authors": [ | ||||
| 		"Eugene Wissner" | ||||
| 	], | ||||
|  | ||||
| 	"targetType": "library", | ||||
|  | ||||
| 	"configurations": [ | ||||
| 		{ | ||||
| 			"name": "library" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
							
								
								
									
										641
									
								
								source/tanya/container/buffer.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										641
									
								
								source/tanya/container/buffer.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,641 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.container.buffer; | ||||
|  | ||||
| import tanya.memory; | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| version (unittest) | ||||
| { | ||||
| 	private int fillBuffer(void* buffer, | ||||
| 	                       in size_t size, | ||||
| 	                       int start = 0, | ||||
| 	                       int end = 10) | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(start < end); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		ubyte[] buf = cast(ubyte[]) buffer[0..size]; | ||||
| 		auto numberRead = end - start; | ||||
|  | ||||
| 		for (ubyte i; i < numberRead; ++i) | ||||
| 		{ | ||||
| 			buf[i] = cast(ubyte) (start + i); | ||||
| 		} | ||||
| 		return numberRead; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Interface for implemeting input/output buffers. | ||||
|  */ | ||||
| interface Buffer | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Returns: The size of the internal buffer. | ||||
| 	 */ | ||||
| 	@property size_t capacity() const @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Data size. | ||||
| 	 */ | ||||
| 	@property size_t length() const @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Available space. | ||||
| 	 */ | ||||
| 	@property size_t free() const @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Appends some data to the buffer. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	buffer = Buffer chunk got with $(D_PSYMBOL buffer). | ||||
| 	 */ | ||||
| 	Buffer opOpAssign(string op)(void[] buffer) | ||||
| 		if (op == "~"); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Buffer that can be used with C functions accepting void pointer and | ||||
|  * returning the number of the read bytes. | ||||
|  */ | ||||
| class ReadBuffer : Buffer | ||||
| { | ||||
| @nogc: | ||||
| 	/// Internal buffer. | ||||
| 	protected ubyte[] _buffer; | ||||
|  | ||||
| 	/// Filled buffer length. | ||||
| 	protected size_t _length; | ||||
|  | ||||
| 	/// Available space. | ||||
| 	protected immutable size_t minAvailable; | ||||
|  | ||||
| 	/// Size by which the buffer will grow. | ||||
| 	protected immutable size_t blockSize; | ||||
|  | ||||
| 	private Allocator allocator; | ||||
|  | ||||
| 	invariant | ||||
| 	{ | ||||
| 		assert(_length <= _buffer.length); | ||||
| 		assert(blockSize > 0); | ||||
| 		assert(minAvailable > 0); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 *  size         = Initial buffer size and the size by which the buffer | ||||
| 	 * 	               will grow. | ||||
| 	 * 	minAvailable = minimal size should be always  available to fill. | ||||
| 	 * 	               So it will reallocate if $(D_INLINECODE  | ||||
| 	 * 	               $(D_PSYMBOL free) < $(D_PARAM minAvailable) | ||||
| 	 * 	               ). | ||||
| 	 */ | ||||
| 	this(size_t size = 8192, | ||||
| 	     size_t minAvailable = 1024, | ||||
| 	     Allocator allocator = defaultAllocator) | ||||
| 	{ | ||||
| 		this.allocator = allocator; | ||||
| 		this.minAvailable = minAvailable; | ||||
| 		this.blockSize = size; | ||||
| 		resizeArray!ubyte(this.allocator, _buffer, size); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Deallocates the internal buffer. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		finalize(allocator, _buffer); | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!ReadBuffer(defaultAllocator); | ||||
| 		assert(b.capacity == 8192); | ||||
| 		assert(b.length == 0); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The size of the internal buffer. | ||||
| 	 */ | ||||
| 	@property size_t capacity() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return _buffer.length; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Data size. | ||||
| 	 */ | ||||
| 	@property size_t length() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return _length; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Available space. | ||||
| 	 */ | ||||
| 	@property size_t free() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return capacity - length; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!ReadBuffer(defaultAllocator); | ||||
| 		size_t numberRead; | ||||
| 		void* buf; | ||||
|  | ||||
| 		// Fills the buffer with values 0..10 | ||||
| 		assert(b.free == b.blockSize); | ||||
| 		buf =  b.buffer; | ||||
| 		numberRead = fillBuffer(buf, b.free, 0, 10); | ||||
| 		b ~= buf[0..numberRead]; | ||||
| 		assert(b.free == b.blockSize - numberRead); | ||||
| 		b[]; | ||||
| 		assert(b.free == b.blockSize); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns a pointer to a chunk of the internal buffer. You can pass it to | ||||
| 	 * a function that requires such a buffer. | ||||
| 	 * | ||||
| 	 * Set the buffer again after reading something into it. Append | ||||
| 	 * $(D_KEYWORD ~=) a slice from the beginning of the buffer you got and | ||||
| 	 * till the number of the read bytes. The data will be appended to the | ||||
| 	 * existing buffer. | ||||
| 	 * | ||||
| 	 * Returns: A chunk of available buffer. | ||||
| 	 */ | ||||
| 	@property void* buffer() | ||||
| 	{ | ||||
| 		if (capacity - length < minAvailable) | ||||
| 		{ | ||||
| 			resizeArray!ubyte(this.allocator, _buffer, capacity + blockSize); | ||||
| 		} | ||||
| 		return _buffer[_length..$].ptr; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Appends some data to the buffer. Use only the buffer you got | ||||
| 	 * with $(D_PSYMBOL buffer)! | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	buffer = Buffer chunk got with $(D_PSYMBOL buffer). | ||||
| 	 */ | ||||
| 	ReadBuffer opOpAssign(string op)(void[] buffer) | ||||
| 		if (op == "~") | ||||
| 	{ | ||||
| 		_length += buffer.length; | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!ReadBuffer(defaultAllocator); | ||||
| 		size_t numberRead; | ||||
| 		void* buf; | ||||
| 		ubyte[] result; | ||||
|  | ||||
| 		// Fills the buffer with values 0..10 | ||||
| 		buf =  b.buffer; | ||||
| 		numberRead = fillBuffer(buf, b.free, 0, 10); | ||||
| 		b ~= buf[0..numberRead]; | ||||
|  | ||||
| 		result = b[]; | ||||
| 		assert(result[0] == 0); | ||||
| 		assert(result[1] == 1); | ||||
| 		assert(result[9] == 9); | ||||
|  | ||||
| 		// It shouldn't overwrite, but append another 5 bytes to the buffer | ||||
| 		buf =  b.buffer; | ||||
| 		numberRead = fillBuffer(buf, b.free, 0, 10); | ||||
| 		b ~= buf[0..numberRead]; | ||||
|  | ||||
| 		buf =  b.buffer; | ||||
| 		numberRead = fillBuffer(buf, b.free, 20, 25); | ||||
| 		b ~= buf[0..numberRead]; | ||||
|  | ||||
| 		result = b[]; | ||||
| 		assert(result[0] == 0); | ||||
| 		assert(result[1] == 1); | ||||
| 		assert(result[9] == 9); | ||||
| 		assert(result[10] == 20); | ||||
| 		assert(result[14] == 24); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns the buffer. The buffer is cleared after that. So you can get it | ||||
| 	 * only one time. | ||||
| 	 * | ||||
| 	 * Returns: The buffer as array. | ||||
| 	 */ | ||||
| 	@property ubyte[] opIndex() | ||||
| 	{ | ||||
| 		auto ret = _buffer[0.._length]; | ||||
| 		_length = 0; | ||||
| 		return ret; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!ReadBuffer(defaultAllocator); | ||||
| 		size_t numberRead; | ||||
| 		void* buf; | ||||
| 		ubyte[] result; | ||||
|  | ||||
| 		// Fills the buffer with values 0..10 | ||||
| 		buf =  b.buffer; | ||||
| 		numberRead = fillBuffer(buf, b.free, 0, 10); | ||||
| 		b ~= buf[0..numberRead]; | ||||
|  | ||||
| 		assert(b.length == 10); | ||||
| 		result = b[]; | ||||
| 		assert(result[0] == 0); | ||||
| 		assert(result[9] == 9); | ||||
| 		assert(b.length == 0); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Circular, self-expanding buffer that can be used with C functions accepting | ||||
|  * void pointer and returning the number of the read bytes. | ||||
|  * | ||||
|  * The buffer is optimized for situations where you read all the data from it | ||||
|  * at once (without writing to it occasionally). It can become ineffective if | ||||
|  * you permanently keep some data in the buffer and alternate writing and | ||||
|  * reading, because it may allocate and move elements. | ||||
|  */ | ||||
| class WriteBuffer : Buffer | ||||
| { | ||||
| @nogc: | ||||
| 	/// Internal buffer. | ||||
| 	protected ubyte[] _buffer; | ||||
|  | ||||
| 	/// Buffer start position. | ||||
| 	protected size_t start; | ||||
|  | ||||
| 	/// Buffer ring area size. After this position begins buffer overflow area. | ||||
| 	protected size_t ring; | ||||
|  | ||||
| 	/// Size by which the buffer will grow. | ||||
| 	protected immutable size_t blockSize; | ||||
|  | ||||
| 	/// The position of the free area in the buffer. | ||||
| 	protected size_t position; | ||||
|  | ||||
| 	private Allocator allocator; | ||||
|  | ||||
| 	invariant | ||||
| 	{ | ||||
| 		assert(blockSize > 0); | ||||
| 		// position can refer to an element outside the buffer if the buffer is full. | ||||
| 		assert(position <= _buffer.length); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 *  size = Initial buffer size and the size by which the buffer | ||||
| 	 * 	       will grow. | ||||
| 	 */ | ||||
| 	this(size_t size = 8192, | ||||
| 	     Allocator allocator = defaultAllocator) | ||||
| 	{ | ||||
| 		this.allocator = allocator; | ||||
| 		blockSize = size; | ||||
| 		ring = size - 1; | ||||
| 		resizeArray!ubyte(this.allocator, _buffer, size); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Deallocates the internal buffer. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		finalize(allocator, _buffer); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The size of the internal buffer. | ||||
| 	 */ | ||||
| 	@property size_t capacity() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return _buffer.length; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Note that $(D_PSYMBOL length) doesn't return the real length of the data, | ||||
| 	 * but only the array length that will be returned with $(D_PSYMBOL buffer) | ||||
| 	 * next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_PSYMBOL written) | ||||
| 	 * until $(D_PSYMBOL length) returns 0. | ||||
| 	 * | ||||
| 	 * Returns: Data size. | ||||
| 	 */ | ||||
| 	@property size_t length() const @safe pure nothrow | ||||
| 	{ | ||||
| 		if (position > ring || position < start) // Buffer overflowed | ||||
| 		{ | ||||
| 			return ring - start + 1; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			return position - start; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!WriteBuffer(defaultAllocator, 4); | ||||
| 		ubyte[3] buf = [48, 23, 255]; | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		assert(b.length == 3); | ||||
| 		b.written = 2; | ||||
| 		assert(b.length == 1); | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		assert(b.length == 2); | ||||
| 		b.written = 2; | ||||
| 		assert(b.length == 2); | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		assert(b.length == 5); | ||||
| 		b.written = b.length; | ||||
| 		assert(b.length == 0); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Available space. | ||||
| 	 */ | ||||
| 	@property size_t free() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return capacity - length; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Appends data to the buffer. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	buffer = Buffer chunk got with $(D_PSYMBOL buffer). | ||||
| 	 */ | ||||
| 	WriteBuffer opOpAssign(string op)(ubyte[] buffer) | ||||
| 		if (op == "~") | ||||
| 	{ | ||||
| 		size_t end, start; | ||||
|  | ||||
| 		if (position >= this.start && position <= ring) | ||||
| 		{ | ||||
| 			auto afterRing = ring + 1; | ||||
|  | ||||
| 			end = position + buffer.length; | ||||
| 			if (end > afterRing) | ||||
| 			{ | ||||
| 				end = afterRing; | ||||
| 			} | ||||
| 			start = end - position; | ||||
| 			_buffer[position..end] = buffer[0..start]; | ||||
| 			if (end == afterRing) | ||||
| 			{ | ||||
| 				position = this.start == 0 ? afterRing : 0; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				position = end; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Check if we have some free space at the beginning | ||||
| 		if (start < buffer.length && position < this.start) | ||||
| 		{ | ||||
| 			end = position + buffer.length - start; | ||||
| 			if (end > this.start) | ||||
| 			{ | ||||
| 				end = this.start; | ||||
| 			} | ||||
| 			auto areaEnd = end - position + start; | ||||
| 			_buffer[position..end] = buffer[start..areaEnd]; | ||||
| 			position = end == this.start ? ring + 1 : end - position; | ||||
| 			start = areaEnd; | ||||
| 		} | ||||
|  | ||||
| 		// And if we still haven't found any place, save the rest in the overflow area | ||||
| 		if (start < buffer.length) | ||||
| 		{ | ||||
| 			end = position + buffer.length - start; | ||||
| 			if (end > capacity) | ||||
| 			{ | ||||
| 				auto newSize = end / blockSize * blockSize + blockSize; | ||||
|  | ||||
| 				resizeArray!ubyte(this.allocator, _buffer, newSize); | ||||
| 			} | ||||
| 			_buffer[position..end] = buffer[start..$]; | ||||
| 			position = end; | ||||
| 			if (this.start == 0) | ||||
| 			{ | ||||
| 				ring = capacity - 1; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!WriteBuffer(defaultAllocator, 4); | ||||
| 		ubyte[3] buf = [48, 23, 255]; | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		assert(b.capacity == 4); | ||||
| 		assert(b._buffer[0] == 48 && b._buffer[1] == 23 && b._buffer[2] == 255); | ||||
|  | ||||
| 		b.written = 2; | ||||
| 		b ~= buf; | ||||
| 		assert(b.capacity == 4); | ||||
| 		assert(b._buffer[0] == 23 && b._buffer[1] == 255 | ||||
| 		    && b._buffer[2] == 255 && b._buffer[3] == 48); | ||||
|  | ||||
| 		b.written = 2; | ||||
| 		b ~= buf; | ||||
| 		assert(b.capacity == 8); | ||||
| 		assert(b._buffer[0] == 23 && b._buffer[1] == 255 | ||||
| 		    && b._buffer[2] == 48 && b._buffer[3] == 23 && b._buffer[4] == 255); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
|  | ||||
| 		b = make!WriteBuffer(defaultAllocator, 2); | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		assert(b.start == 0); | ||||
| 		assert(b.capacity == 4); | ||||
| 		assert(b.ring == 3); | ||||
| 		assert(b.position == 3); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets how many bytes were written. It will shrink the buffer | ||||
| 	 * appropriately. Always set this property after calling | ||||
| 	 * $(D_PSYMBOL buffer). | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	length = Length of the written data. | ||||
| 	 */ | ||||
| 	@property void written(size_t length) @safe pure nothrow | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(length <= this.length); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		auto afterRing = ring + 1; | ||||
|         auto oldStart = start; | ||||
|  | ||||
| 		if (length <= 0) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
|         else if (position <= afterRing) | ||||
|         { | ||||
|             start += length; | ||||
|             if (start > 0 && position == afterRing) | ||||
|             { | ||||
|                 position = oldStart; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             auto overflow = position - afterRing; | ||||
|  | ||||
|             if (overflow > length) { | ||||
|                 _buffer[start.. start + length] = _buffer[afterRing.. afterRing + length]; | ||||
|                 _buffer[afterRing.. afterRing + length] = _buffer[afterRing + length ..position]; | ||||
|                 position -= length; | ||||
|             } | ||||
|             else if (overflow == length) | ||||
|             { | ||||
|                 _buffer[start.. start + overflow] = _buffer[afterRing..position]; | ||||
|                 position -= overflow; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _buffer[start.. start + overflow] = _buffer[afterRing..position]; | ||||
|                 position = overflow; | ||||
|             } | ||||
|             start += length; | ||||
|  | ||||
|             if (start == position) | ||||
|             { | ||||
|                 if (position != afterRing) | ||||
|                 { | ||||
|                     position = 0; | ||||
|                 } | ||||
|                 start = 0; | ||||
|                 ring = capacity - 1; | ||||
|             } | ||||
|         } | ||||
|         if (start > ring) | ||||
|         { | ||||
|             start = 0; | ||||
|         } | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!WriteBuffer(defaultAllocator); | ||||
| 		ubyte[6] buf = [23, 23, 255, 128, 127, 9]; | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		assert(b.length == 6); | ||||
| 		b.written = 2; | ||||
| 		assert(b.length == 4); | ||||
| 		b.written = 4; | ||||
| 		assert(b.length == 0); | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns a pointer to a buffer chunk with data. You can pass it to | ||||
| 	 * a function that requires such a buffer. | ||||
| 	 * | ||||
| 	 * After calling it, set $(D_PSYMBOL written) to the length could be | ||||
| 	 * written. | ||||
| 	 * | ||||
| 	 * $(D_PSYMBOL buffer) may return only part of the data. You may need | ||||
| 	 * to call it (and set $(D_PSYMBOL written) several times until | ||||
| 	 * $(D_PSYMBOL length) is 0. If all the data can be written, | ||||
| 	 * maximally 3 calls are required. | ||||
| 	 * | ||||
| 	 * Returns: A chunk of data buffer. | ||||
| 	 */ | ||||
| 	@property void* buffer() @safe pure nothrow | ||||
| 	{ | ||||
| 		if (position > ring || position < start) // Buffer overflowed | ||||
| 		{ | ||||
| 			return _buffer[start.. ring + 1].ptr; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			return _buffer[start..position].ptr; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto b = make!WriteBuffer(defaultAllocator, 6); | ||||
| 		ubyte[6] buf = [23, 23, 255, 128, 127, 9]; | ||||
| 		void* returnedBuf; | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		returnedBuf = b.buffer; | ||||
| 		assert(returnedBuf[0..b.length] == buf[0..6]); | ||||
| 		b.written = 2; | ||||
|  | ||||
| 		returnedBuf = b.buffer; | ||||
| 		assert(returnedBuf[0..b.length] == buf[2..6]); | ||||
|  | ||||
| 		b ~= buf; | ||||
| 		returnedBuf = b.buffer; | ||||
| 		assert(returnedBuf[0..b.length] == buf[2..6]); | ||||
| 		b.written = b.length; | ||||
|  | ||||
| 		returnedBuf = b.buffer; | ||||
| 		assert(returnedBuf[0..b.length] == buf[0..6]); | ||||
| 		b.written = b.length; | ||||
|  | ||||
| 		finalize(defaultAllocator, b); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										405
									
								
								source/tanya/container/list.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								source/tanya/container/list.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,405 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.container.list; | ||||
|  | ||||
| import tanya.memory; | ||||
|  | ||||
| /** | ||||
|  * Singly linked list. | ||||
|  * | ||||
|  * Params: | ||||
|  * 	T = Content type. | ||||
|  */ | ||||
| class SList(T) | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Creates a new $(D_PSYMBOL SList). | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	allocator = The allocator should be used for the element | ||||
| 	 * 	            allocations. | ||||
| 	 */ | ||||
| 	this(Allocator allocator = defaultAllocator) | ||||
| 	{ | ||||
| 		this.allocator = allocator; | ||||
|         reset(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Removes all elements from the list. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		while (!empty) | ||||
| 		{ | ||||
|             static if (isFinalizable!T) | ||||
|             { | ||||
|                 finalize(allocator, front); | ||||
|             } | ||||
| 			popFront(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: First element. | ||||
| 	 */ | ||||
| 	@property ref T front() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return first.next.content; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Inserts a new element at the beginning. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	x = New element. | ||||
| 	 */ | ||||
| 	@property void front(T x) | ||||
| 	{ | ||||
| 		Entry* temp = make!Entry(allocator); | ||||
| 		 | ||||
| 		temp.content = x; | ||||
| 		temp.next = first.next; | ||||
| 		first.next = temp; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int[2] values = [8, 9]; | ||||
|  | ||||
| 		l.front = values[0]; | ||||
| 		assert(l.front == values[0]); | ||||
| 		l.front = values[1]; | ||||
| 		assert(l.front == values[1]); | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Inserts a new element at the beginning. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	x = New element. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
| 	 */ | ||||
| 	typeof(this) opOpAssign(string Op)(ref T x) | ||||
| 		if (Op == "~") | ||||
| 	{ | ||||
| 		front = x; | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int value = 5; | ||||
|  | ||||
| 		assert(l.empty); | ||||
|  | ||||
| 		l ~= value; | ||||
|  | ||||
| 		assert(l.front == value); | ||||
| 		assert(!l.empty); | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: $(D_KEYWORD true) if the list is empty. | ||||
| 	 */ | ||||
| 	@property bool empty() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return first.next is null; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns the first element and moves to the next one. | ||||
| 	 * | ||||
| 	 * Returns: The first element. | ||||
| 	 */ | ||||
| 	T popFront() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		auto n = first.next.next; | ||||
| 		auto content = first.next.content; | ||||
|  | ||||
| 		finalize(allocator, first.next); | ||||
| 		first.next = n; | ||||
|  | ||||
|         return content; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int[2] values = [8, 9]; | ||||
|  | ||||
| 		l.front = values[0]; | ||||
| 		l.front = values[1]; | ||||
| 		assert(l.front == values[1]); | ||||
| 		l.popFront(); | ||||
| 		assert(l.front == values[0]); | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns the current item from the list and removes from the list. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	x = The item should be removed. | ||||
| 	 * | ||||
| 	 * Returns: Removed item. | ||||
| 	 */ | ||||
| 	T remove() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		auto temp = position.next.next; | ||||
| 		auto content = position.next.content; | ||||
|  | ||||
| 		finalize(allocator, position.next); | ||||
| 		position.next = temp; | ||||
|  | ||||
| 		return content; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int[3] values = [8, 5, 4]; | ||||
|  | ||||
| 		l.front = values[0]; | ||||
| 		l.front = values[1]; | ||||
| 		assert(l.remove() == 5); | ||||
| 		l.front = values[2]; | ||||
| 		assert(l.remove() == 4); | ||||
| 		assert(l.remove() == 8); | ||||
| 		assert(l.empty); | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
|     /** | ||||
|      * Resets the current position. | ||||
|      * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
|      */ | ||||
|     typeof(this) reset() | ||||
|     { | ||||
|         position = &first; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int[2] values = [8, 5]; | ||||
|  | ||||
| 		l.current = values[0]; | ||||
| 		l.current = values[1]; | ||||
| 		assert(l.current == 5); | ||||
| 		l.advance(); | ||||
| 		assert(l.current == 8); | ||||
| 		l.reset(); | ||||
| 		assert(l.current == 5); | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * $(D_KEYWORD foreach) iteration. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	dg = $(D_KEYWORD foreach) body. | ||||
| 	 */ | ||||
| 	int opApply(int delegate(ref size_t i, ref T) @nogc dg) | ||||
| 	{ | ||||
| 		int result; | ||||
| 		size_t i; | ||||
|  | ||||
| 		for (position = first.next; position; position = position.next, ++i) | ||||
| 		{ | ||||
| 			result = dg(i, position.content); | ||||
|  | ||||
| 			if (result != 0) | ||||
| 			{ | ||||
| 				return result; | ||||
| 			} | ||||
| 		} | ||||
| 		reset(); | ||||
|  | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int[3] values = [5, 4, 9]; | ||||
|  | ||||
| 		l.front = values[0]; | ||||
| 		l.front = values[1]; | ||||
| 		l.front = values[2]; | ||||
| 		foreach (i, e; l) | ||||
| 		{ | ||||
| 			assert(i != 0 || e == values[2]); | ||||
| 			assert(i != 1 || e == values[1]); | ||||
| 			assert(i != 2 || e == values[0]); | ||||
| 		} | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	int opApply(int delegate(ref T) @nogc dg) | ||||
| 	{ | ||||
| 		int result; | ||||
|  | ||||
| 		for (position = first.next; position; position = position.next) | ||||
| 		{ | ||||
| 			result = dg(position.content); | ||||
|  | ||||
| 			if (result != 0) | ||||
| 			{ | ||||
| 				return result; | ||||
| 			} | ||||
| 		} | ||||
| 		reset(); | ||||
|  | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto l = make!(SList!int)(defaultAllocator); | ||||
| 		int[3] values = [5, 4, 9]; | ||||
| 		size_t i; | ||||
|  | ||||
| 		l.front = values[0]; | ||||
| 		l.front = values[1]; | ||||
| 		l.front = values[2]; | ||||
| 		foreach (e; l) | ||||
| 		{ | ||||
| 			assert(i != 0 || e == values[2]); | ||||
| 			assert(i != 1 || e == values[1]); | ||||
| 			assert(i != 2 || e == values[0]); | ||||
| 			++i; | ||||
| 		} | ||||
|  | ||||
| 		finalize(defaultAllocator, l); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: $(D_KEYWORD true) if the current position is the end position. | ||||
| 	 */ | ||||
| 	@property bool end() const | ||||
| 	{ | ||||
| 		return empty || position.next.next is null; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Moves to the next element and returns it. | ||||
| 	 * | ||||
| 	 * Returns: The element on the next position. | ||||
| 	 */ | ||||
| 	T advance() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!end); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		position = position.next; | ||||
| 		return position.content; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Element on the current position. | ||||
| 	 */ | ||||
| 	@property ref T current() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return position.next.content; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Inserts a new element at the current position. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	x = New element. | ||||
| 	 */ | ||||
| 	@property void current(T x) | ||||
| 	{ | ||||
| 		Entry* temp = make!Entry(allocator); | ||||
| 		 | ||||
| 		temp.content = x; | ||||
| 		temp.next = position.next; | ||||
| 		position.next = temp; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * List entry. | ||||
| 	 */ | ||||
| 	protected struct Entry | ||||
| 	{ | ||||
| 		/// List item content. | ||||
| 		T content; | ||||
|  | ||||
| 		/// Next list item. | ||||
| 		Entry* next; | ||||
| 	} | ||||
|  | ||||
| 	/// 0th element of the list. | ||||
| 	protected Entry first; | ||||
|  | ||||
| 	/// Current position in the list. | ||||
| 	protected Entry* position; | ||||
|  | ||||
| 	private Allocator allocator; | ||||
| } | ||||
|  | ||||
| interface Stuff | ||||
| { | ||||
| } | ||||
|  | ||||
| /// | ||||
| unittest | ||||
| { | ||||
| 	auto l = make!(SList!Stuff)(defaultAllocator); | ||||
|  | ||||
| 	finalize(defaultAllocator, l); | ||||
| } | ||||
							
								
								
									
										16
									
								
								source/tanya/container/package.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								source/tanya/container/package.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.container; | ||||
|  | ||||
| public import tanya.container.buffer; | ||||
| public import tanya.container.list; | ||||
| public import tanya.container.vector; | ||||
| public import tanya.container.queue; | ||||
							
								
								
									
										219
									
								
								source/tanya/container/queue.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								source/tanya/container/queue.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.container.queue; | ||||
|  | ||||
| import tanya.memory; | ||||
|  | ||||
| /** | ||||
|  * Queue. | ||||
|  * | ||||
|  * Params: | ||||
|  * 	T = Content type. | ||||
|  */ | ||||
| class Queue(T) | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Creates a new $(D_PSYMBOL Queue). | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	allocator = The allocator should be used for the element | ||||
| 	 * 	            allocations. | ||||
| 	 */ | ||||
| 	this(Allocator allocator = defaultAllocator) | ||||
| 	{ | ||||
| 		this.allocator = allocator; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Removes all elements from the queue. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		foreach (e; this) | ||||
| 		{ | ||||
|             static if (isFinalizable!T) | ||||
|             { | ||||
|                 finalize(allocator, e); | ||||
|             } | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: First element. | ||||
| 	 */ | ||||
| 	@property ref T front() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return first.next.content; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Inserts a new element. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	x = New element. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
| 	 */ | ||||
| 	typeof(this) insertBack(T x) | ||||
| 	{ | ||||
| 		Entry* temp = make!Entry(allocator); | ||||
| 		 | ||||
| 		temp.content = x; | ||||
|  | ||||
| 		if (empty) | ||||
| 		{ | ||||
| 			first.next = rear = temp; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			rear.next = temp; | ||||
| 			rear = rear.next; | ||||
| 		} | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	alias insert = insertBack; | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto q = make!(Queue!int)(defaultAllocator); | ||||
| 		int[2] values = [8, 9]; | ||||
|  | ||||
| 		q.insertBack(values[0]); | ||||
| 		assert(q.front is values[0]); | ||||
| 		q.insertBack(values[1]); | ||||
| 		assert(q.front is values[0]); | ||||
|  | ||||
| 		finalize(defaultAllocator, q); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Inserts a new element. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	x = New element. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
| 	 */ | ||||
| 	typeof(this) opOpAssign(string Op)(ref T x) | ||||
| 		if (Op == "~") | ||||
| 	{ | ||||
| 		return insertBack(x); | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto q = make!(Queue!int)(defaultAllocator); | ||||
| 		int value = 5; | ||||
|  | ||||
| 		assert(q.empty); | ||||
|  | ||||
| 		q ~= value; | ||||
|  | ||||
| 		assert(q.front == value); | ||||
| 		assert(!q.empty); | ||||
|  | ||||
| 		finalize(defaultAllocator, q); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: $(D_KEYWORD true) if the queue is empty. | ||||
| 	 */ | ||||
| 	@property bool empty() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return first.next is null; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto q = make!(Queue!int)(defaultAllocator); | ||||
| 		int value = 7; | ||||
|  | ||||
| 		assert(q.empty); | ||||
| 		q.insertBack(value); | ||||
| 		assert(!q.empty); | ||||
|  | ||||
| 		finalize(defaultAllocator, q); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Move position to the next element. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
| 	 */ | ||||
| 	typeof(this) popFront() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		auto n = first.next.next; | ||||
|  | ||||
| 		finalize(allocator, first.next); | ||||
| 		first.next = n; | ||||
|  | ||||
|         return this; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto q = make!(Queue!int)(defaultAllocator); | ||||
| 		int[2] values = [8, 9]; | ||||
|  | ||||
| 		q.insertBack(values[0]); | ||||
| 		q.insertBack(values[1]); | ||||
| 		assert(q.front is values[0]); | ||||
| 		q.popFront(); | ||||
| 		assert(q.front is values[1]); | ||||
|  | ||||
| 		finalize(defaultAllocator, q); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Queue entry. | ||||
| 	 */ | ||||
| 	protected struct Entry | ||||
| 	{ | ||||
| 		/// Queue item content. | ||||
| 		T content; | ||||
|  | ||||
| 		/// Next list item. | ||||
| 		Entry* next; | ||||
| 	} | ||||
|  | ||||
| 	/// The first element of the list. | ||||
| 	protected Entry first; | ||||
|  | ||||
| 	/// The last element of the list. | ||||
| 	protected Entry* rear; | ||||
|  | ||||
| 	private Allocator allocator; | ||||
| } | ||||
|  | ||||
| /// | ||||
| unittest | ||||
| { | ||||
| 	auto q = make!(Queue!int)(defaultAllocator); | ||||
|  | ||||
| 	finalize(defaultAllocator, q); | ||||
| } | ||||
							
								
								
									
										420
									
								
								source/tanya/container/vector.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								source/tanya/container/vector.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,420 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.container.vector; | ||||
|  | ||||
| import tanya.memory; | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| /** | ||||
|  * One dimensional array. It allocates automatically if needed. | ||||
|  * | ||||
|  * If you assign a value: | ||||
|  * --- | ||||
|  * auto v = make!(Vector!int)(defaultAllocator); | ||||
|  * int value = 5; | ||||
|  * | ||||
|  * v[1000] = value; | ||||
|  * | ||||
|  * finalize(defaultAllocator, v); | ||||
|  * --- | ||||
|  * it will allocate not only for one, but for 1000 elements. So this | ||||
|  * implementation is more suitable for sequential data with random access. | ||||
|  * | ||||
|  * Params: | ||||
|  * 	T = Content type. | ||||
|  */ | ||||
| class Vector(T) | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Creates a new $(D_PSYMBOL Vector). | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	length    = Initial length. | ||||
| 	 * 	allocator = The allocator should be used for the element | ||||
| 	 * 	            allocations. | ||||
| 	 */ | ||||
| 	this(size_t length, Allocator allocator = defaultAllocator) | ||||
| 	{ | ||||
| 		this.allocator = allocator; | ||||
| 		vector = makeArray!T(allocator, length); | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	this(Allocator allocator = defaultAllocator) | ||||
| 	{ | ||||
| 		this(0, allocator); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Removes all elements from the vector. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		finalize(allocator, vector); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Vector length. | ||||
| 	 */ | ||||
| 	@property size_t length() const | ||||
| 	{ | ||||
| 		return vector.length; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Expans/shrinks the vector. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	length = New length. | ||||
| 	 */ | ||||
| 	@property void length(size_t length) | ||||
| 	{ | ||||
| 		resizeArray!T(allocator, vector, length); | ||||
| 	} | ||||
|  | ||||
|     /// | ||||
|     unittest | ||||
|     { | ||||
|         auto v = make!(Vector!int)(defaultAllocator); | ||||
|  | ||||
|         v.length = 5; | ||||
|         assert(v.length == 5); | ||||
|  | ||||
| 		// TODO | ||||
|         v.length = 7; | ||||
|         assert(v.length == 7); | ||||
|  | ||||
|         v.length = 0; | ||||
|         assert(v.length == 0); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
|     } | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: $(D_KEYWORD true) if the vector is empty. | ||||
| 	 */ | ||||
| 	@property bool empty() const | ||||
| 	{ | ||||
| 		return length == 0; | ||||
| 	} | ||||
|  | ||||
| 	static if (isFinalizable!T) | ||||
| 	{ | ||||
| 		/** | ||||
| 		 * Removes an elements from the vector. | ||||
| 		 * | ||||
| 		 * Params: | ||||
| 		 * 	pos = Element index. | ||||
| 		 */ | ||||
| 		void remove(size_t pos) | ||||
| 		{ | ||||
| 			auto el = vector[pos]; | ||||
| 			finalize(allocator, el); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Assigns a value. Allocates if needed. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	value = Value. | ||||
| 	 * | ||||
| 	 * Returns: Assigned value. | ||||
| 	 */ | ||||
| 	T opIndexAssign(T value, size_t pos) | ||||
| 	{ | ||||
| 		if (pos >= length) | ||||
| 		{ | ||||
| 			resizeArray!T(allocator, vector, pos + 1); | ||||
| 		} | ||||
| 		return vector[pos] = value; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
|         auto v = make!(Vector!int)(defaultAllocator); | ||||
|         int[2] values = [5, 15]; | ||||
|  | ||||
| 		assert(v.length == 0); | ||||
| 		v[1] = values[0]; | ||||
| 		assert(v.length == 2); | ||||
| 		v[3] = values[0]; | ||||
| 		assert(v.length == 4); | ||||
| 		v[4] = values[1]; | ||||
| 		assert(v.length == 5); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The value on index $(D_PARAM pos). | ||||
| 	 */ | ||||
| 	ref T opIndex(in size_t pos) | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(length > pos); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return vector[pos]; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
|         auto v = make!(Vector!int)(defaultAllocator); | ||||
|         int[2] values = [5, 15]; | ||||
|  | ||||
| 		v[1] = values[0]; | ||||
| 		assert(v[1] is values[0]); | ||||
| 		v[3] = values[0]; | ||||
| 		assert(v[3] is values[0]); | ||||
| 		v[4] = values[1]; | ||||
| 		assert(v[4] is values[1]); | ||||
| 		v[0] = values[1]; | ||||
| 		assert(v[0] is values[1]); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * $(D_KEYWORD foreach) iteration. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	dg = $(D_KEYWORD foreach) body. | ||||
| 	 */ | ||||
| 	int opApply(int delegate(ref T) @nogc dg) | ||||
| 	{ | ||||
| 		int result; | ||||
|  | ||||
| 		foreach (e; vector) | ||||
| 		{ | ||||
| 			result = dg(e); | ||||
|  | ||||
| 			if (result != 0) | ||||
| 			{ | ||||
| 				return result; | ||||
| 			} | ||||
| 		} | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	int opApply(int delegate(ref size_t i, ref T) @nogc dg) | ||||
| 	{ | ||||
| 		int result; | ||||
|  | ||||
| 		foreach (i, e; vector) | ||||
| 		{ | ||||
| 			result = dg(i, e); | ||||
|  | ||||
| 			if (result != 0) | ||||
| 			{ | ||||
| 				return result; | ||||
| 			} | ||||
| 		} | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
|         auto v = make!(Vector!int)(defaultAllocator, 1); | ||||
|         int[3] values = [5, 15, 8]; | ||||
|  | ||||
|         v[0] = values[0]; | ||||
|         v[1] = values[1]; | ||||
|         v[2] = values[2]; | ||||
|  | ||||
| 		int i; | ||||
| 		foreach (e; v) | ||||
| 		{ | ||||
| 			assert(i != 0 || e is values[0]); | ||||
| 			assert(i != 1 || e is values[1]); | ||||
| 			assert(i != 2 || e is values[2]); | ||||
| 			++i; | ||||
| 		} | ||||
|  | ||||
| 		foreach (j, e; v) | ||||
| 		{ | ||||
| 			assert(j != 0 || e is values[0]); | ||||
| 			assert(j != 1 || e is values[1]); | ||||
| 			assert(j != 2 || e is values[2]); | ||||
| 		} | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the first element. Allocates if the vector is empty. | ||||
|      * | ||||
|      * Params: | ||||
|      *  x = New element. | ||||
|      */ | ||||
| 	@property void front(ref T x) | ||||
| 	{ | ||||
| 		this[0] = x; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The first element. | ||||
|      */ | ||||
| 	@property ref inout(T) front() inout | ||||
|     in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return vector[0]; | ||||
| 	} | ||||
|  | ||||
|     /// | ||||
|     unittest | ||||
|     { | ||||
|         auto v = make!(Vector!int)(defaultAllocator, 1); | ||||
|         int[2] values = [5, 15]; | ||||
|  | ||||
|         v.front = values[0]; | ||||
|         assert(v.front == 5); | ||||
|  | ||||
|         v.front = values[1]; | ||||
|         assert(v.front == 15); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
|     } | ||||
|  | ||||
| 	/** | ||||
| 	 * Move position to the next element. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
| 	 */ | ||||
| 	typeof(this) popFront() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		vector[0 .. $ - 1] = vector[1..$]; | ||||
| 		resizeArray(allocator, vector, length - 1); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
|         auto v = make!(Vector!int)(defaultAllocator, 1); | ||||
|         int[2] values = [5, 15]; | ||||
|  | ||||
|         v[0] = values[0]; | ||||
|         v[1] = values[1]; | ||||
|         assert(v.front is values[0]); | ||||
|         assert(v.length == 2); | ||||
| 		v.popFront(); | ||||
|         assert(v.front is values[1]); | ||||
|         assert(v.length == 1); | ||||
| 		v.popFront(); | ||||
|         assert(v.empty); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the last element. Allocates if the vector is empty. | ||||
|      * | ||||
|      * Params: | ||||
|      *  x = New element. | ||||
|      */ | ||||
| 	@property void back(ref T x) | ||||
| 	{ | ||||
| 		vector[empty ? 0 : $ - 1] = x; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The last element. | ||||
|      */ | ||||
| 	@property ref inout(T) back() inout | ||||
|     in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return vector[$ - 1]; | ||||
| 	} | ||||
|  | ||||
|     /// | ||||
|     unittest | ||||
|     { | ||||
|         auto v = make!(Vector!int)(defaultAllocator, 1); | ||||
|         int[2] values = [5, 15]; | ||||
|  | ||||
|         v.back = values[0]; | ||||
|         assert(v.back == 5); | ||||
|  | ||||
|         v.back = values[1]; | ||||
|         assert(v.back == 15); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
|     } | ||||
|  | ||||
| 	/** | ||||
| 	 * Move position to the previous element. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
| 	 */ | ||||
| 	typeof(this) popBack() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!empty); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		resizeArray(allocator, vector, length - 1); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
|         auto v = make!(Vector!int)(defaultAllocator, 1); | ||||
|         int[2] values = [5, 15]; | ||||
|  | ||||
|         v[0] = values[0]; | ||||
|         v[1] = values[1]; | ||||
|         assert(v.back is values[1]); | ||||
|         assert(v.length == 2); | ||||
| 		v.popBack(); | ||||
|         assert(v.back is values[0]); | ||||
|         assert(v.length == 1); | ||||
| 		v.popBack(); | ||||
|         assert(v.empty); | ||||
|  | ||||
|         finalize(defaultAllocator, v); | ||||
| 	} | ||||
|  | ||||
| 	/// Container. | ||||
| 	protected T[] vector; | ||||
|  | ||||
| 	private Allocator allocator; | ||||
| } | ||||
|  | ||||
| /// | ||||
| unittest | ||||
| { | ||||
| 	auto v = make!(Vector!int)(defaultAllocator); | ||||
|  | ||||
| 	finalize(defaultAllocator, v); | ||||
| } | ||||
							
								
								
									
										191
									
								
								source/tanya/crypto/padding.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								source/tanya/crypto/padding.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.crypto.padding; | ||||
|  | ||||
| import tanya.memory; | ||||
| import std.algorithm.iteration; | ||||
| import std.typecons; | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| /** | ||||
|  * Supported padding mode. | ||||
|  * | ||||
|  * See_Also: | ||||
|  * 	$(D_PSYMBOL applyPadding) | ||||
|  */ | ||||
| enum Mode | ||||
| { | ||||
| 	zero, | ||||
| 	pkcs7, | ||||
| 	ansiX923, | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Params: | ||||
|  * 	allocator = Allocator that should be used if the block should be extended | ||||
|  * 	            or a new block should be added. | ||||
|  * 	input     = Sequence that should be padded. | ||||
|  * 	mode      = Padding mode. | ||||
|  * 	blockSize = Block size. | ||||
|  * | ||||
|  * Returns: The functions modifies the initial array and returns it. | ||||
|  * | ||||
|  * See_Also: | ||||
|  * 	$(D_PSYMBOL Mode) | ||||
|  */ | ||||
| ubyte[] applyPadding(ref ubyte[] input, | ||||
|                      in Mode mode, | ||||
|                      in ushort blockSize, | ||||
|                      Allocator allocator = defaultAllocator) | ||||
| in | ||||
| { | ||||
| 	assert(blockSize > 0 && blockSize <= 256); | ||||
| 	assert(blockSize % 64 == 0); | ||||
| 	assert(input.length > 0); | ||||
| } | ||||
| body | ||||
| { | ||||
| 	immutable rest = cast(ubyte) input.length % blockSize; | ||||
| 	immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize); | ||||
| 	immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0); | ||||
|  | ||||
| 	final switch (mode) with (Mode) | ||||
| 	{ | ||||
| 		case zero: | ||||
| 			allocator.expandArray(input, needed); | ||||
| 			break; | ||||
| 		case pkcs7: | ||||
| 			if (needed) | ||||
| 			{ | ||||
| 				allocator.expandArray(input, needed); | ||||
| 				input[input.length - needed ..$].each!((ref e) => e = needed); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				allocator.expandArray(input, blockSize); | ||||
| 			} | ||||
| 			break; | ||||
| 		case ansiX923: | ||||
| 			allocator.expandArray(input, needed ? needed : blockSize); | ||||
| 			input[$ - 1] = needed; | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	return input; | ||||
| } | ||||
|  | ||||
| /// | ||||
| unittest | ||||
| { | ||||
| 	{ // Zeros | ||||
| 		auto input = defaultAllocator.makeArray!ubyte(50); | ||||
|  | ||||
| 		applyPadding(input, Mode.zero, 64); | ||||
| 		assert(input.length == 64); | ||||
|  | ||||
| 		applyPadding(input, Mode.zero, 64); | ||||
| 		assert(input.length == 64); | ||||
| 		assert(input[63] == 0); | ||||
|  | ||||
| 		defaultAllocator.finalize(input); | ||||
| 	} | ||||
| 	{ // PKCS#7 | ||||
| 		auto input = defaultAllocator.makeArray!ubyte(50); | ||||
| 		for (ubyte i; i < 40; ++i) | ||||
| 		{ | ||||
| 			input[i] = i; | ||||
| 		} | ||||
|  | ||||
| 		applyPadding(input, Mode.pkcs7, 64); | ||||
| 		assert(input.length == 64); | ||||
| 		for (ubyte i; i < 64; ++i) | ||||
| 		{ | ||||
| 			if (i >= 40 && i < 50) | ||||
| 			{ | ||||
| 				assert(input[i] == 0); | ||||
| 			} | ||||
| 			else if (i >= 50) | ||||
| 			{ | ||||
| 				assert(input[i] == 14); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				assert(input[i] == i); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		applyPadding(input, Mode.pkcs7, 64); | ||||
| 		assert(input.length == 128); | ||||
| 		for (ubyte i; i < 128; ++i) | ||||
| 		{ | ||||
| 			if (i >= 64 || (i >= 40 && i < 50)) | ||||
| 			{ | ||||
| 				assert(input[i] == 0); | ||||
| 			} | ||||
| 			else if (i >= 50 && i < 64) | ||||
| 			{ | ||||
| 				assert(input[i] == 14); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				assert(input[i] == i); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		defaultAllocator.finalize(input); | ||||
| 	} | ||||
| 	{ // ANSI X.923 | ||||
| 		auto input = defaultAllocator.makeArray!ubyte(50); | ||||
| 		for (ubyte i; i < 40; ++i) | ||||
| 		{ | ||||
| 			input[i] = i; | ||||
| 		} | ||||
|  | ||||
| 		applyPadding(input, Mode.ansiX923, 64); | ||||
| 		assert(input.length == 64); | ||||
| 		for (ubyte i; i < 64; ++i) | ||||
| 		{ | ||||
| 			if (i < 40) | ||||
| 			{ | ||||
| 				assert(input[i] == i); | ||||
| 			} | ||||
| 			else if (i == 63) | ||||
| 			{ | ||||
| 				assert(input[i] == 14); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				assert(input[i] == 0); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		applyPadding(input, Mode.pkcs7, 64); | ||||
| 		assert(input.length == 128); | ||||
| 		for (ubyte i = 0; i < 128; ++i) | ||||
| 		{ | ||||
| 			if (i < 40) | ||||
| 			{ | ||||
| 				assert(input[i] == i); | ||||
| 			} | ||||
| 			else if (i == 63) | ||||
| 			{ | ||||
| 				assert(input[i] == 14); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				assert(input[i] == 0); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		defaultAllocator.finalize(input); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										26
									
								
								source/tanya/event/config.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								source/tanya/event/config.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.config; | ||||
|  | ||||
| package version (DisableBackends) | ||||
| { | ||||
| } | ||||
| else | ||||
| { | ||||
| 	version (linux) | ||||
| 	{ | ||||
| 		enum UseEpoll = true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		enum UseEpoll = false; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										226
									
								
								source/tanya/event/internal/epoll.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								source/tanya/event/internal/epoll.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.internal.epoll; | ||||
|  | ||||
| import tanya.event.config; | ||||
|  | ||||
| static if (UseEpoll): | ||||
|  | ||||
| public import core.sys.linux.epoll; | ||||
| import tanya.event.internal.selector; | ||||
| import tanya.event.protocol; | ||||
| import tanya.event.transport; | ||||
| import tanya.event.watcher; | ||||
| import tanya.event.loop; | ||||
| import tanya.container.list; | ||||
| import tanya.memory; | ||||
| import core.stdc.errno; | ||||
| import core.sys.posix.fcntl; | ||||
| import core.sys.posix.netinet.in_; | ||||
| import core.time; | ||||
| import std.algorithm.comparison; | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| extern (C) nothrow | ||||
| { // TODO: Make a pull request for Phobos to mark this extern functions as @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 accept4(int, sockaddr*, socklen_t*, int flags); | ||||
| } | ||||
|  | ||||
| private enum maxEvents = 128; | ||||
|  | ||||
| class EpollLoop : Loop | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Initializes the loop. | ||||
| 	 */ | ||||
| 	this() | ||||
| 	{ | ||||
| 		super(); | ||||
|  | ||||
| 		if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| 		epollEvents = makeArray!epoll_event(defaultAllocator, maxEvents).ptr; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Frees loop internals. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		finalize(defaultAllocator, epollEvents); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Should be called if the backend configuration changes. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	socket    = Socket. | ||||
| 	 * 	oldEvents = The events were already set. | ||||
| 	 * 	events    = The events should be set. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD true) if the operation was successful. | ||||
| 	 */ | ||||
| 	protected override bool modify(int socket, EventMask oldEvents, EventMask events) | ||||
| 	{ | ||||
| 		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; | ||||
| 		} | ||||
|  | ||||
| 		ev.data.fd = socket; | ||||
| 		ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0) | ||||
| 		          | (events & Event.write ? EPOLLOUT : 0) | ||||
| 		          | EPOLLET; | ||||
|  | ||||
| 		return epoll_ctl(fd, op, socket, &ev) == 0; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Accept incoming connections. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	protocolFactory = Protocol factory. | ||||
| 	 * 	socket          = Socket. | ||||
| 	 */ | ||||
| 	protected override void acceptConnection(Protocol delegate() @nogc protocolFactory, | ||||
| 	                                         int socket) | ||||
| 	{ | ||||
| 		sockaddr_in client_addr; | ||||
| 		socklen_t client_len = client_addr.sizeof; | ||||
| 		int client = accept4(socket, | ||||
| 		                     cast(sockaddr *)&client_addr, | ||||
| 		                     &client_len, | ||||
| 		                     O_NONBLOCK); | ||||
| 		while (client >= 0) | ||||
| 		{ | ||||
| 			auto transport = make!SocketTransport(defaultAllocator, this, client); | ||||
| 			IOWatcher connection; | ||||
|  | ||||
| 			if (connections.length > client) | ||||
| 			{ | ||||
| 				connection = cast(IOWatcher) connections[client]; | ||||
| 				// If it is a ConnectionWatcher | ||||
| 				if (connection is null && connections[client] !is null) | ||||
| 				{ | ||||
| 					finalize(defaultAllocator, connections[client]); | ||||
| 					connections[client] = null; | ||||
| 				} | ||||
| 			} | ||||
| 			if (connection !is null) | ||||
| 			{ | ||||
| 				connection(protocolFactory, transport); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				connections[client] = make!IOWatcher(defaultAllocator, | ||||
| 				                                     protocolFactory, | ||||
| 				                                     transport); | ||||
| 			} | ||||
|  | ||||
| 			modify(client, EventMask(Event.none), EventMask(Event.read, Event.write)); | ||||
|  | ||||
| 			swapPendings.insertBack(connections[client]); | ||||
|  | ||||
| 			client = accept4(socket, | ||||
| 			                 cast(sockaddr *)&client_addr, | ||||
| 			                 &client_len, | ||||
| 			                 O_NONBLOCK); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Does the actual polling. | ||||
| 	 */ | ||||
| 	protected override void poll() | ||||
| 	{ | ||||
| 		// Don't block | ||||
| 		immutable timeout = cast(immutable int) blockTime.total!"msecs"; | ||||
| 		auto eventCount = epoll_wait(fd, epollEvents, maxEvents, timeout); | ||||
|  | ||||
| 		if (eventCount < 0) | ||||
| 		{ | ||||
| 			if (errno != EINTR) | ||||
| 			{ | ||||
| 				throw make!BadLoopException(defaultAllocator); | ||||
| 			} | ||||
|  | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		for (auto i = 0; i < eventCount; ++i) | ||||
| 		{ | ||||
| 			epoll_event *ev = epollEvents + i; | ||||
| 			auto connection = cast(IOWatcher) connections[ev.data.fd]; | ||||
|  | ||||
| 			if (connection is null) | ||||
| 			{ | ||||
| 				swapPendings.insertBack(connections[ev.data.fd]); | ||||
| //				acceptConnection(connections[ev.data.fd].protocol, | ||||
| //				                 connections[ev.data.fd].socket); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				auto transport = cast(SocketTransport) connection.transport; | ||||
| 				assert(transport !is null); | ||||
|  | ||||
| 				if (ev.events & (EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP)) | ||||
| 				{ | ||||
| 					try | ||||
| 					{ | ||||
| 						while (!transport.receive()) | ||||
| 						{ | ||||
| 						} | ||||
| 						swapPendings.insertBack(connection); | ||||
| 					} | ||||
| 					catch (TransportException e) | ||||
| 					{ | ||||
| 						swapPendings.insertBack(connection); | ||||
| 						finalize(defaultAllocator, e); | ||||
| 					} | ||||
| 				} | ||||
| 				else if (ev.events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) | ||||
| 				{ | ||||
| 					transport.writeReady = true; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The blocking time. | ||||
| 	 */ | ||||
| 	override protected @property inout(Duration) blockTime() | ||||
| 	inout @safe pure nothrow | ||||
| 	{ | ||||
| 		return min(super.blockTime, 1.dur!"seconds"); | ||||
| 	} | ||||
|  | ||||
| 	private int fd; | ||||
| 	private epoll_event* epollEvents; | ||||
| } | ||||
							
								
								
									
										217
									
								
								source/tanya/event/internal/selector.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								source/tanya/event/internal/selector.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.internal.selector; | ||||
|  | ||||
| import tanya.memory; | ||||
| import tanya.container.buffer; | ||||
| import tanya.event.loop; | ||||
| import tanya.event.protocol; | ||||
| import tanya.event.transport; | ||||
| import core.stdc.errno; | ||||
| import core.sys.posix.netinet.in_; | ||||
| import core.sys.posix.unistd; | ||||
|  | ||||
| /** | ||||
|  * Transport for stream sockets. | ||||
|  */ | ||||
| class SocketTransport : DuplexTransport | ||||
| { | ||||
| @nogc: | ||||
| 	private int socket_ = -1; | ||||
|  | ||||
| 	private Protocol protocol_; | ||||
|  | ||||
| 	/// Input buffer. | ||||
| 	private WriteBuffer input_; | ||||
|  | ||||
| 	/// Output buffer. | ||||
| 	private ReadBuffer output_; | ||||
|  | ||||
| 	private Loop loop; | ||||
|  | ||||
| 	private bool disconnected_; | ||||
|  | ||||
| 	package bool writeReady; | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	loop     = Event loop. | ||||
| 	 * 	socket   = Socket. | ||||
| 	 * 	protocol = Protocol. | ||||
| 	 */ | ||||
| 	this(Loop loop, int socket, Protocol protocol = null) | ||||
| 	{ | ||||
| 		socket_ = socket; | ||||
| 		protocol_ = protocol; | ||||
| 		this.loop = loop; | ||||
| 		input_ = make!WriteBuffer(defaultAllocator); | ||||
| 		output_ = make!ReadBuffer(defaultAllocator); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Close the transport and deallocate the data buffers. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		close(socket); | ||||
| 		finalize(defaultAllocator, input_); | ||||
| 		finalize(defaultAllocator, output_); | ||||
| 		finalize(defaultAllocator, protocol_); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Transport socket. | ||||
| 	 */ | ||||
| 	int socket() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return socket_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Protocol. | ||||
| 	 */ | ||||
| 	@property Protocol protocol() @safe pure nothrow | ||||
| 	{ | ||||
| 		return protocol_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 *  Returns: $(D_KEYWORD true) if the remote peer closed the connection, | ||||
| 	 *           $(D_KEYWORD false) otherwise. | ||||
| 	 */ | ||||
| 	@property immutable(bool) disconnected() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return disconnected_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	protocol = Application protocol. | ||||
| 	 */ | ||||
| 	@property void protocol(Protocol protocol) @safe pure nothrow | ||||
| 	{ | ||||
| 		protocol_ = protocol; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Application protocol. | ||||
| 	 */ | ||||
| 	@property inout(Protocol) protocol() inout @safe pure nothrow | ||||
| 	{ | ||||
| 		return protocol_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Write some data to the transport. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	data = Data to send. | ||||
| 	 */ | ||||
| 	void write(ubyte[] data) | ||||
| 	{ | ||||
| 		// If the buffer wasn't empty the transport should be already there. | ||||
| 		if (!input.length && data.length) | ||||
| 		{ | ||||
| 			loop.feed(this); | ||||
| 		} | ||||
| 		input ~= data; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Input buffer. | ||||
| 	 */ | ||||
| 	@property WriteBuffer input() @safe pure nothrow | ||||
| 	{ | ||||
| 		return input_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Output buffer. | ||||
| 	 */ | ||||
| 	@property ReadBuffer output() @safe pure nothrow | ||||
| 	{ | ||||
| 		return output_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Read data from the socket. Returns $(D_KEYWORD true) if the reading | ||||
| 	 * is completed. In the case that the peer closed the connection, returns | ||||
| 	 * $(D_KEYWORD true) aswell. | ||||
| 	 * | ||||
| 	 * Returns: Whether the reading is completed. | ||||
| 	 * | ||||
| 	 * Throws: $(D_PSYMBOL TransportException) if a read error is occured. | ||||
| 	 */ | ||||
| 	bool receive() | ||||
| 	{ | ||||
| 		auto readCount = recv(socket, output.buffer, output.free, 0); | ||||
|  | ||||
| 		if (readCount > 0) | ||||
| 		{ | ||||
| 			output_ ~= output.buffer[0..readCount]; | ||||
| 			return false; | ||||
| 		} | ||||
| 		else if (readCount == 0) | ||||
| 		{ | ||||
| 			disconnected_ = true; | ||||
| 			return true; | ||||
| 		} | ||||
| 		else if (errno == EAGAIN || errno == EWOULDBLOCK) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			disconnected_ = true; | ||||
| 			throw make!TransportException(defaultAllocator, | ||||
| 			                              "Read from the socket failed."); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Whether the writing is completed. | ||||
| 	 * | ||||
| 	 * Throws: $(D_PSYMBOL TransportException) if a read error is occured. | ||||
| 	 */ | ||||
| 	bool send() | ||||
| 	{ | ||||
| 		auto sentCount = core.sys.posix.netinet.in_.send(socket, | ||||
| 		                                                 input.buffer, | ||||
| 		                                                 input.length, | ||||
| 		                                                 0); | ||||
|  | ||||
| 		input.written = sentCount; | ||||
| 		if (input.length == 0) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 		else if (sentCount >= 0) | ||||
| 		{ | ||||
| 			loop.feed(this); | ||||
|  | ||||
| 			return false; | ||||
| 		} | ||||
| 		else if (errno == EAGAIN || errno == EWOULDBLOCK) | ||||
| 		{ | ||||
| 			writeReady = false; | ||||
| 			loop.feed(this); | ||||
|  | ||||
| 			return false; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			disconnected_ = true; | ||||
| 			loop.feed(this); | ||||
| 			throw make!TransportException(defaultAllocator, | ||||
| 			                              "Write to the socket failed."); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										279
									
								
								source/tanya/event/loop.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								source/tanya/event/loop.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.loop; | ||||
|  | ||||
| import tanya.memory; | ||||
| import tanya.container.queue; | ||||
| import tanya.container.vector; | ||||
| import tanya.event.config; | ||||
| import tanya.event.protocol; | ||||
| import tanya.event.transport; | ||||
| import tanya.event.watcher; | ||||
| import tanya.container.buffer; | ||||
| import core.thread; | ||||
| import core.time; | ||||
| import std.algorithm.iteration; | ||||
| import std.algorithm.mutation; | ||||
| import std.typecons; | ||||
|  | ||||
| static if (UseEpoll) | ||||
| { | ||||
|     import tanya.event.internal.epoll; | ||||
| } | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| /** | ||||
|  * Events. | ||||
|  */ | ||||
| enum Event : uint | ||||
| { | ||||
| 	none   = 0x00, /// No events. | ||||
| 	read   = 0x01, /// Non-blocking read call. | ||||
| 	write  = 0x02, /// Non-blocking write call. | ||||
| 	accept = 0x04, /// Connection made. | ||||
| } | ||||
|  | ||||
| alias EventMask = BitFlags!Event; | ||||
|  | ||||
| /** | ||||
|  * Event loop. | ||||
|  */ | ||||
| abstract class Loop | ||||
| { | ||||
| @nogc: | ||||
| 	/// Pending watchers. | ||||
| 	protected Queue!Watcher pendings; | ||||
|  | ||||
| 	protected Queue!Watcher swapPendings; | ||||
|  | ||||
| 	/// Pending connections. | ||||
| 	protected Vector!ConnectionWatcher connections; | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes the loop. | ||||
| 	 */ | ||||
| 	this() | ||||
| 	{ | ||||
| 		connections = make!(Vector!ConnectionWatcher)(defaultAllocator); | ||||
| 		pendings = make!(Queue!Watcher)(defaultAllocator); | ||||
| 		swapPendings = make!(Queue!Watcher)(defaultAllocator); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Frees loop internals. | ||||
| 	 */ | ||||
| 	~this() | ||||
| 	{ | ||||
| 		finalize(defaultAllocator, connections); | ||||
| 		finalize(defaultAllocator, pendings); | ||||
| 		finalize(defaultAllocator, swapPendings); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Starts the loop. | ||||
| 	 */ | ||||
|     void run() | ||||
|     { | ||||
| 		done_ = false; | ||||
| 		do | ||||
| 		{ | ||||
| 			poll(); | ||||
|  | ||||
| 			// Invoke pendings | ||||
| 			swapPendings.each!((ref p) => p.invoke()); | ||||
|  | ||||
| 			swap(pendings, swapPendings); | ||||
| 		} | ||||
| 		while (!done_); | ||||
|     } | ||||
|  | ||||
| 	/** | ||||
| 	 * Break out of the loop. | ||||
| 	 */ | ||||
|     void unloop() @safe pure nothrow | ||||
|     { | ||||
| 		done_ = true; | ||||
|     } | ||||
|  | ||||
| 	/** | ||||
| 	 * Start watching. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	watcher = Watcher. | ||||
| 	 */ | ||||
| 	void start(ConnectionWatcher watcher) | ||||
| 	{ | ||||
| 		if (watcher.active) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| 		watcher.active = true; | ||||
| 		watcher.accept = &acceptConnection; | ||||
| 		connections[watcher.socket] = watcher; | ||||
|  | ||||
| 		modify(watcher.socket, EventMask(Event.none), EventMask(Event.accept)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Stop watching. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	watcher = Watcher. | ||||
| 	 */ | ||||
| 	void stop(ConnectionWatcher watcher) | ||||
| 	{ | ||||
| 		if (!watcher.active) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| 		watcher.active = false; | ||||
|  | ||||
| 		modify(watcher.socket, EventMask(Event.accept), EventMask(Event.none)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Feeds the given event set into the event loop, as if the specified event | ||||
| 	 * had happened for the specified watcher. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	transport = Affected transport. | ||||
| 	 */ | ||||
| 	void feed(DuplexTransport transport) | ||||
| 	{ | ||||
| 		pendings.insertBack(connections[transport.socket]); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Should be called if the backend configuration changes. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	socket    = Socket. | ||||
| 	 * 	oldEvents = The events were already set. | ||||
| 	 * 	events    = The events should be set. | ||||
| 	 * | ||||
| 	 * Returns: $(D_KEYWORD true) if the operation was successful. | ||||
| 	 */ | ||||
| 	protected bool modify(int socket, EventMask oldEvents, EventMask events); | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: The blocking time. | ||||
| 	 */ | ||||
| 	protected @property inout(Duration) blockTime() | ||||
| 	inout @safe pure nothrow | ||||
| 	{ | ||||
| 		// Don't block if we have to do. | ||||
| 		return swapPendings.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 | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(blockTime <= 1.dur!"hours", "Too long to wait."); | ||||
| 		assert(!blockTime.isNegative); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		blockTime_ = blockTime; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Does the actual polling. | ||||
| 	 */ | ||||
| 	protected void poll(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Accept incoming connections. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	protocolFactory = Protocol factory. | ||||
| 	 * 	socket          = Socket. | ||||
| 	 */ | ||||
| 	protected void acceptConnection(Protocol delegate() @nogc protocolFactory, | ||||
| 	                                int socket); | ||||
|  | ||||
| 	/// Whether the event loop should be stopped. | ||||
|     private bool done_; | ||||
|  | ||||
| 	/// Maximal block time. | ||||
| 	protected Duration blockTime_ = 1.dur!"minutes"; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Exception thrown on errors in the event loop. | ||||
|  */ | ||||
| class BadLoopException : Exception | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * 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 @safe nothrow const | ||||
| 	{ | ||||
| 		super("Event loop cannot be initialized.", file, line, next); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns the event loop used by default. If an event loop wasn't set with | ||||
|  * $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL getDefaultLoop()) will try to | ||||
|  * choose an event loop supported on the system. | ||||
|  * | ||||
|  * Returns: The default event loop. | ||||
|  */ | ||||
| Loop getDefaultLoop() | ||||
| { | ||||
|     if (_defaultLoop !is null) | ||||
|     { | ||||
| 		return _defaultLoop; | ||||
| 	} | ||||
|  | ||||
| 	static if (UseEpoll) | ||||
| 	{ | ||||
| 		_defaultLoop = make!EpollLoop(defaultAllocator); | ||||
| 	} | ||||
|  | ||||
|     return _defaultLoop; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Sets the default event loop. | ||||
|  * | ||||
|  * This property makes it possible to implement your own backends or event | ||||
|  * loops, for example, if the system is not supported or if you want to | ||||
|  * extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass | ||||
|  * your implementation to this property. | ||||
|  * | ||||
|  * Params: | ||||
|  * 	loop = The event loop. | ||||
|  */ | ||||
| @property void defaultLoop(Loop loop) | ||||
| in | ||||
| { | ||||
| 	assert(loop !is null); | ||||
| } | ||||
| body | ||||
| { | ||||
| 	_defaultLoop = loop; | ||||
| } | ||||
|  | ||||
| private Loop _defaultLoop; | ||||
							
								
								
									
										16
									
								
								source/tanya/event/package.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								source/tanya/event/package.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event; | ||||
|  | ||||
| public import tanya.event.loop; | ||||
| public import tanya.event.protocol; | ||||
| public import tanya.event.transport; | ||||
| public import tanya.event.watcher; | ||||
							
								
								
									
										46
									
								
								source/tanya/event/protocol.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								source/tanya/event/protocol.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.protocol; | ||||
|  | ||||
| import tanya.event.transport; | ||||
|  | ||||
| /** | ||||
|  * Common protocol interface. | ||||
|  */ | ||||
| interface Protocol | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	data = Read data. | ||||
| 	 */ | ||||
| 	void received(ubyte[] data); | ||||
|  | ||||
| 	/** | ||||
| 	 * Called when a connection is made. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	transport = Protocol transport. | ||||
| 	 */ | ||||
| 	void connected(DuplexTransport transport); | ||||
|  | ||||
| 	/** | ||||
| 	 * Called when a connection is lost. | ||||
| 	 */ | ||||
| 	void disconnected(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Interface for TCP. | ||||
|  */ | ||||
| interface TransmissionControlProtocol  : Protocol | ||||
| { | ||||
| } | ||||
							
								
								
									
										138
									
								
								source/tanya/event/transport.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								source/tanya/event/transport.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.transport; | ||||
|  | ||||
| import tanya.container.buffer; | ||||
| import tanya.event.protocol; | ||||
|  | ||||
| /** | ||||
|  * Exception thrown on read/write errors. | ||||
|  */ | ||||
| class TransportException : Exception | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	msg  = Message to output. | ||||
| 	 * 	file = The file where the exception occurred. | ||||
| 	 * 	line = The line number where the exception occurred. | ||||
| 	 * 	next = The previous exception in the chain of exceptions, if any. | ||||
| 	 */ | ||||
| 	this(string msg, | ||||
| 	     string file = __FILE__, | ||||
| 	     size_t line = __LINE__, | ||||
| 	     Throwable next = null) pure @safe nothrow const | ||||
| 	{ | ||||
| 		super(msg, file, line, next); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Base transport interface. | ||||
|  */ | ||||
| interface Transport | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Returns: Protocol. | ||||
| 	 */ | ||||
| 	@property Protocol protocol() @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: $(D_KEYWORD true) if the peer closed the connection, | ||||
| 	 *          $(D_KEYWORD false) otherwise. | ||||
| 	 */ | ||||
| 	@property immutable(bool) disconnected() const @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	protocol = Application protocol. | ||||
| 	 */ | ||||
| 	@property void protocol(Protocol protocol) @safe pure nothrow | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(protocol !is null, "protocolConnected cannot be unset."); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Application protocol. | ||||
| 	 */ | ||||
| 	@property inout(Protocol) protocol() inout @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Transport socket. | ||||
| 	 */ | ||||
| 	int socket() const @safe pure nothrow; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Interface for read-only transports. | ||||
|  */ | ||||
| interface ReadTransport : Transport | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Returns: Underlying output buffer. | ||||
| 	 */ | ||||
| 	@property ReadBuffer output(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Reads data into the buffer. | ||||
| 	 * | ||||
| 	 * Returns: Whether the reading is completed. | ||||
| 	 * | ||||
| 	 * Throws: $(D_PSYMBOL TransportException) if a read error is occured. | ||||
| 	 */ | ||||
| 	bool receive() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(!disconnected); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Interface for write-only transports. | ||||
|  */ | ||||
| interface WriteTransport : Transport | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Returns: Underlying input buffer. | ||||
| 	 */ | ||||
| 	@property WriteBuffer input(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Write some data to the transport. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	data = Data to send. | ||||
| 	 */ | ||||
| 	void write(ubyte[] data); | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Whether the writing is completed. | ||||
| 	 * | ||||
| 	 * Throws: $(D_PSYMBOL TransportException) if a read error is occured. | ||||
| 	 */ | ||||
| 	bool send() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(input.length); | ||||
| 		assert(!disconnected); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Represents a bidirectional transport. | ||||
|  */ | ||||
| abstract class DuplexTransport : ReadTransport, WriteTransport | ||||
| { | ||||
| } | ||||
							
								
								
									
										210
									
								
								source/tanya/event/watcher.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								source/tanya/event/watcher.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.event.watcher; | ||||
|  | ||||
| import tanya.event.protocol; | ||||
| import tanya.event.transport; | ||||
| import tanya.memory; | ||||
| import std.functional; | ||||
|  | ||||
| /** | ||||
|  * A watcher is an opaque structure that you allocate and register to record | ||||
|  * your interest in some event.  | ||||
|  */ | ||||
| abstract class Watcher | ||||
| { | ||||
| @nogc: | ||||
|     /// Whether the watcher is active. | ||||
|     bool active; | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoke some action on event. | ||||
| 	 */ | ||||
| 	void invoke(); | ||||
| } | ||||
|  | ||||
| class ConnectionWatcher : Watcher | ||||
| { | ||||
| @nogc: | ||||
| 	/// Watched file descriptor. | ||||
|     private int socket_; | ||||
|  | ||||
| 	/// Protocol factory. | ||||
| 	protected Protocol delegate() protocolFactory; | ||||
|  | ||||
| 	/// Callback. | ||||
| 	package void delegate(Protocol delegate() @nogc protocolFactory, | ||||
| 	                      int socket) accept; | ||||
|  | ||||
| 	invariant | ||||
| 	{ | ||||
| 		assert(socket_ >= 0, "Called with negative file descriptor."); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	protocolFactory = Function returning a new $(D_PSYMBOL Protocol) instance. | ||||
| 	 * 	socket          = Socket. | ||||
| 	 */ | ||||
| 	this(Protocol function() @nogc protocolFactory, int socket) | ||||
| 	{ | ||||
| 		this.protocolFactory = toDelegate(protocolFactory); | ||||
| 		socket_ = socket; | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	this(Protocol delegate() @nogc protocolFactory, int socket) | ||||
| 	{ | ||||
| 		this.protocolFactory = protocolFactory; | ||||
| 		socket_ = socket; | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	protected this(Protocol function() @nogc protocolFactory) | ||||
| 	{ | ||||
| 		this.protocolFactory = toDelegate(protocolFactory); | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	protected this(Protocol delegate() @nogc protocolFactory) | ||||
| 	{ | ||||
| 		this.protocolFactory = protocolFactory; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Socket. | ||||
| 	 */ | ||||
| 	@property inout(int) socket() inout @safe pure nothrow | ||||
| 	{ | ||||
| 		return socket_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Application protocol factory. | ||||
| 	 */ | ||||
| 	@property inout(Protocol delegate() @nogc) protocol() inout | ||||
| 	{ | ||||
| 		return protocolFactory; | ||||
| 	} | ||||
|  | ||||
| 	override void invoke() | ||||
| 	{ | ||||
| 		accept(protocol, socket); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Contains a pending watcher with the invoked events or a transport can be | ||||
|  * read from. | ||||
|  */ | ||||
| class IOWatcher : ConnectionWatcher | ||||
| { | ||||
| @nogc: | ||||
| 	/// References a watcher or a transport. | ||||
| 	DuplexTransport transport_; | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	protocolFactory = Function returning application specific protocol. | ||||
| 	 * 	transport       = Transport. | ||||
| 	 */ | ||||
| 	this(Protocol delegate() @nogc protocolFactory, | ||||
| 		 DuplexTransport transport) | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(transport !is null); | ||||
| 		assert(protocolFactory !is null); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		super(protocolFactory); | ||||
| 		this.transport_ = transport; | ||||
| 	} | ||||
|  | ||||
| 	~this() | ||||
| 	{ | ||||
| 		finalize(defaultAllocator, transport_); | ||||
| 	} | ||||
|  | ||||
|     /** | ||||
|      * Assigns a transport. | ||||
|      * | ||||
|      * Params: | ||||
| 	 * 	protocolFactory = Function returning application specific protocol. | ||||
| 	 * 	transport       = Transport. | ||||
|      * | ||||
| 	 * Returns: $(D_KEYWORD this). | ||||
|      */ | ||||
| 	IOWatcher opCall(Protocol delegate() @nogc protocolFactory, | ||||
| 	                 DuplexTransport transport) @safe pure nothrow | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(transport !is null); | ||||
| 		assert(protocolFactory !is null); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		this.protocolFactory = protocolFactory; | ||||
|         this.transport_ = transport; | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Transport used by this watcher. | ||||
| 	 */ | ||||
| 	@property inout(DuplexTransport) transport() inout @safe pure nothrow | ||||
| 	{ | ||||
| 		return transport_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Socket. | ||||
| 	 */ | ||||
| 	override @property inout(int) socket() inout @safe pure nothrow | ||||
| 	{ | ||||
| 		return transport.socket; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Invokes the watcher callback. | ||||
| 	 * | ||||
| 	 * Finalizes the transport on disconnect. | ||||
| 	 */ | ||||
| 	override void invoke() | ||||
| 	{ | ||||
| 		if (transport.protocol is null) | ||||
| 		{ | ||||
| 			transport.protocol = protocolFactory(); | ||||
| 			transport.protocol.connected(transport); | ||||
| 		} | ||||
| 		else if (transport.disconnected) | ||||
| 		{ | ||||
| 			transport.protocol.disconnected(); | ||||
| 			finalize(defaultAllocator, transport_); | ||||
| 			protocolFactory = null; | ||||
| 		} | ||||
| 		else if (transport.output.length) | ||||
| 		{ | ||||
| 			transport.protocol.received(transport.output[]); | ||||
| 		} | ||||
| 		else if (transport.input.length) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				transport.send(); | ||||
| 			} | ||||
| 			catch (TransportException e) | ||||
| 			{ | ||||
| 				finalize(defaultAllocator, e); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										112
									
								
								source/tanya/math.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								source/tanya/math.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.container.math; | ||||
|  | ||||
| version (unittest) | ||||
| { | ||||
| 	import std.algorithm.iteration; | ||||
| } | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| /** | ||||
|  * Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z). | ||||
|  * | ||||
|  * Params: | ||||
|  * 	x = Base. | ||||
|  * 	y = Exponent. | ||||
|  * 	z = Divisor. | ||||
|  * | ||||
|  * Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y) | ||||
|  *          by $(D_PARAM z). | ||||
|  */ | ||||
| ulong pow(ulong x, ulong y, ulong z) @safe nothrow pure | ||||
| in | ||||
| { | ||||
| 	assert(z > 0); | ||||
| } | ||||
| out (result) | ||||
| { | ||||
| 	assert(result >= 0); | ||||
| } | ||||
| body | ||||
| { | ||||
|     ulong mask = ulong.max / 2 + 1, result; | ||||
|  | ||||
| 	if (y == 0) | ||||
| 	{ | ||||
| 		return 1 % z; | ||||
| 	} | ||||
| 	else if (y == 1) | ||||
| 	{ | ||||
| 		return x % z; | ||||
| 	} | ||||
|     do | ||||
|     { | ||||
|         auto bit = y & mask; | ||||
|         if (!result && bit) | ||||
|         { | ||||
|             result = x; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         result *= result; | ||||
|         if (bit) | ||||
|         { | ||||
|             result *= x; | ||||
|         } | ||||
|         result %= z; | ||||
|  | ||||
|     } | ||||
|     while (mask >>= 1); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| /// | ||||
| unittest | ||||
| { | ||||
|     assert(pow(3, 5, 7) == 5); | ||||
|     assert(pow(2, 2, 1) == 0); | ||||
|     assert(pow(3, 3, 3) == 0); | ||||
|     assert(pow(7, 4, 2) == 1); | ||||
|     assert(pow(53, 0, 2) == 1); | ||||
|     assert(pow(53, 1, 3) == 2); | ||||
|     assert(pow(53, 2, 5) == 4); | ||||
|     assert(pow(0, 0, 5) == 1); | ||||
|     assert(pow(0, 5, 5) == 0); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks if $(D_PARAM x) is a prime. | ||||
|  * | ||||
|  * Params: | ||||
|  * 	x = The number should be checked. | ||||
|  * | ||||
|  * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number, | ||||
|  *          $(D_KEYWORD false) otherwise. | ||||
|  */ | ||||
| bool isPseudoprime(ulong x) @safe nothrow pure | ||||
| { | ||||
| 	return pow(2, x - 1, x) == 1; | ||||
| } | ||||
|  | ||||
| /// | ||||
| unittest | ||||
| { | ||||
| 	uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, | ||||
|                       74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821, | ||||
| 	                  74827, 9973, 104729, 15485867, 49979693, 104395303, | ||||
| 	                  593441861, 104729, 15485867, 49979693, 104395303, | ||||
| 	                  593441861, 899809363, 982451653]; | ||||
|  | ||||
| 	known.each!((ref x) => assert(isPseudoprime(x))); | ||||
| } | ||||
							
								
								
									
										58
									
								
								source/tanya/memory/allocator.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								source/tanya/memory/allocator.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.memory.allocator; | ||||
|  | ||||
| /** | ||||
|  * This interface should be similar to $(D_PSYMBOL | ||||
|  * std.experimental.allocator.IAllocator), but usable in | ||||
|  * $(D_KEYWORD @nogc)-code. | ||||
|  */ | ||||
| interface Allocator | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Allocates $(D_PARAM s) bytes of memory. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	s = Amount of memory to allocate. | ||||
| 	 * | ||||
| 	 * Returns: The pointer to the new allocated memory. | ||||
| 	 */ | ||||
|     void[] allocate(size_t s) @safe; | ||||
|  | ||||
|     /** | ||||
| 	 * Deallocates a memory block. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	p = A pointer to the memory block to be freed. | ||||
| 	 * | ||||
| 	 * Returns: Whether the deallocation was successful. | ||||
| 	 */ | ||||
|     bool deallocate(void[] p) @safe; | ||||
|  | ||||
| 	/** | ||||
| 	 * Increases or decreases the size of a memory block. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	p    = A pointer to the memory block. | ||||
| 	 * 	size = Size of the reallocated block. | ||||
| 	 * | ||||
| 	 * Returns: Whether the reallocation was successful. | ||||
| 	 */ | ||||
| 	bool reallocate(ref void[] p, size_t s) @safe; | ||||
|  | ||||
| 	/** | ||||
|      * Static allocator instance and initializer. | ||||
| 	 * | ||||
| 	 * Returns: An $(D_PSYMBOL Allocator) instance. | ||||
| 	 */ | ||||
| 	static @property Allocator instance() @safe; | ||||
| } | ||||
							
								
								
									
										207
									
								
								source/tanya/memory/package.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								source/tanya/memory/package.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.memory; | ||||
|  | ||||
| public import tanya.memory.allocator; | ||||
| public import std.experimental.allocator : make, makeArray, expandArray, shrinkArray, IAllocator; | ||||
| import core.atomic; | ||||
| import core.stdc.stdlib; | ||||
| import std.traits; | ||||
|  | ||||
| version (Windows) | ||||
| { | ||||
| 	import core.sys.windows.windows; | ||||
| } | ||||
| else version (Posix) | ||||
| { | ||||
| 	public import tanya.memory.ullocator; | ||||
| 	import core.sys.posix.pthread; | ||||
| } | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| version (Windows) | ||||
| { | ||||
| 	package alias Mutex = CRITICAL_SECTION; | ||||
| 	package alias destroyMutex = DeleteCriticalSection; | ||||
| } | ||||
| else version (Posix) | ||||
| { | ||||
| 	package alias Mutex = pthread_mutex_t; | ||||
|     package void destroyMutex(pthread_mutex_t* mtx) | ||||
|     { | ||||
|         pthread_mutex_destroy(mtx) && assert(0); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @property void defaultAllocator(Allocator allocator) @safe nothrow | ||||
| { | ||||
| 	_defaultAllocator = allocator; | ||||
| } | ||||
|  | ||||
| @property Allocator defaultAllocator() @safe nothrow | ||||
| { | ||||
| 	return _defaultAllocator; | ||||
| } | ||||
|  | ||||
| static this() @safe nothrow | ||||
| { | ||||
| 	defaultAllocator = Ullocator.instance; | ||||
| } | ||||
|  | ||||
| package struct Monitor | ||||
| { | ||||
| 	Object.Monitor impl; // for user-level monitors | ||||
| 	void delegate(Object) @nogc[] devt; // for internal monitors | ||||
| 	size_t refs; // reference count | ||||
| 	version (Posix) | ||||
| 	{ | ||||
| 		Mutex mtx; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| package @property ref shared(Monitor*) monitor(Object h) pure nothrow | ||||
| { | ||||
|     return *cast(shared Monitor**)&h.__monitor; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Destroys and then deallocates (using $(D_PARAM allocator)) the class | ||||
|  * object referred to by a $(D_KEYWORD class) or $(D_KEYWORD interface) | ||||
|  * reference. It is assumed the respective entities had been allocated with | ||||
|  * the same allocator. | ||||
|  * | ||||
|  * Params: | ||||
|  * 	A         = The type of the allocator used for the ojbect allocation. | ||||
|  * 	T         = The type of the object that should be destroyed. | ||||
|  * 	allocator = The allocator used for the object allocation. | ||||
|  * 	p         = The object should be destroyed. | ||||
|  */ | ||||
| void finalize(A, T)(auto ref A allocator, ref T p) | ||||
| 	if (is(T == class) || is(T == interface)) | ||||
| { | ||||
| 	static if (is(T == interface)) | ||||
| 	{ | ||||
| 		auto ob = cast(Object) p; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		alias ob = p; | ||||
| 	} | ||||
| 	auto pp = cast(void*) ob; | ||||
| 	auto ppv = cast(void**) pp; | ||||
| 	if (!pp || !*ppv) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length]; | ||||
| 	auto pc = cast(ClassInfo*) *ppv; | ||||
| 	auto c = *pc; | ||||
| 	do | ||||
| 	{ | ||||
| 		if (c.destructor) | ||||
| 		{ | ||||
| 			(cast(void function(Object)) c.destructor)(ob); | ||||
| 		} | ||||
| 	} while ((c = c.base) !is null); | ||||
|  | ||||
| 	// Take care of monitors for synchronized blocks | ||||
| 	if (ppv[1]) | ||||
| 	{ | ||||
| 		shared(Monitor)* m = atomicLoad!(MemoryOrder.acq)(ob.monitor); | ||||
| 		if (m !is null) | ||||
| 		{ | ||||
| 			auto mc = cast(Monitor*) m; | ||||
| 			if (!atomicOp!("-=")(m.refs, cast(size_t) 1)) | ||||
| 			{ | ||||
| 				foreach (v; mc.devt) | ||||
| 				{ | ||||
| 					if (v) | ||||
| 					{ | ||||
| 						v(ob); | ||||
| 					} | ||||
| 				} | ||||
| 				if (mc.devt.ptr) | ||||
| 				{ | ||||
| 					free(mc.devt.ptr); | ||||
| 				} | ||||
| 				destroyMutex(&mc.mtx); | ||||
| 				free(mc); | ||||
| 				atomicStore!(MemoryOrder.rel)(ob.monitor, null); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	*ppv = null; | ||||
|  | ||||
| 	allocator.deallocate(support); | ||||
| 	p = null; | ||||
| } | ||||
|  | ||||
| /// Ditto. | ||||
| void finalize(A, T)(auto ref A allocator, ref T *p) | ||||
| 	if (is(T == struct)) | ||||
| { | ||||
|     if (p is null) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	static if (hasElaborateDestructor!T) | ||||
| 	{ | ||||
| 		*p.__xdtor(); | ||||
| 	} | ||||
| 	allocator.deallocate((cast(void*)p)[0 .. T.sizeof]); | ||||
| 	p = null; | ||||
| } | ||||
|  | ||||
| /// Ditto. | ||||
| void finalize(A, T)(auto ref A allocator, ref T[] p) | ||||
| { | ||||
| 	static if (hasElaborateDestructor!T) | ||||
| 	{ | ||||
| 		foreach (ref e; p) | ||||
| 		{ | ||||
| 			finalize(allocator, e); | ||||
| 		} | ||||
| 	} | ||||
| 	allocator.deallocate(p); | ||||
| 	p = null; | ||||
| } | ||||
|  | ||||
| bool resizeArray(T, A)(auto ref A allocator, ref T[] array, in size_t length) | ||||
| @trusted | ||||
| { | ||||
| 	if (length == array.length) | ||||
| 	{ | ||||
| 		return true; | ||||
| 	} | ||||
| 	if (array is null && length > 0) | ||||
| 	{ | ||||
| 		array = makeArray!T(allocator, length); | ||||
| 		return array !is null; | ||||
| 	} | ||||
| 	if (length == 0) | ||||
| 	{ | ||||
| 		finalize(allocator, array); | ||||
| 		return true; | ||||
| 	} | ||||
| 	void[] buf = array; | ||||
| 	if (!allocator.reallocate(buf, length * T.sizeof)) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	array = cast(T[]) buf; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| enum bool isFinalizable(T) = is(T == class) || is(T == interface) | ||||
|                           || hasElaborateDestructor!T || isDynamicArray!T; | ||||
|  | ||||
| private Allocator _defaultAllocator; | ||||
							
								
								
									
										423
									
								
								source/tanya/memory/ullocator.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										423
									
								
								source/tanya/memory/ullocator.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,423 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.memory.ullocator; | ||||
|  | ||||
| import tanya.memory.allocator; | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| version (Posix): | ||||
|  | ||||
| import core.sys.posix.sys.mman; | ||||
| import core.sys.posix.unistd; | ||||
|  | ||||
| /** | ||||
|  * Allocator for Posix systems with mmap/munmap support. | ||||
|  * | ||||
|  * This allocator allocates memory in regions (multiple of 4 KB for example). | ||||
|  * Each region is then splitted in blocks. So it doesn't request the memory | ||||
|  * from the operating system on each call, but only if there are no large | ||||
|  * enought free blocks in the available regions. | ||||
|  * Deallocation works in the same way. Deallocation doesn't immediately | ||||
|  * gives the memory back to the operating system, but marks the appropriate | ||||
|  * block as free and only if all blocks in the region are free, the complet | ||||
|  * region is deallocated. | ||||
|  * | ||||
|  * ---------------------------------------------------------------------------- | ||||
|  * |      |     |         |     |            ||      |     |                  | | ||||
|  * |      |prev <-----------    |            ||      |     |                  | | ||||
|  * |  R   |  B  |         |  B  |            ||   R  |  B  |                  | | ||||
|  * |  E   |  L  |         |  L  |           next  E  |  L  |                  | | ||||
|  * |  G   |  O  |  DATA   |  O  |   FREE    --->  G  |  O  |       DATA       | | ||||
|  * |  I   |  C  |         |  C  |           <---  I  |  C  |                  | | ||||
|  * |  O   |  K  |         |  K  |           prev  O  |  K  |                  | | ||||
|  * |  N   |    -----------> next|            ||   N  |     |                  | | ||||
|  * |      |     |         |     |            ||      |     |                  | | ||||
|  * --------------------------------------------------- ------------------------ | ||||
|  */ | ||||
| class Ullocator : Allocator | ||||
| { | ||||
| @nogc: | ||||
| 	@disable this(); | ||||
|  | ||||
| 	shared static this() @safe nothrow | ||||
| 	{ | ||||
| 		pageSize = sysconf(_SC_PAGE_SIZE); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Allocates $(D_PARAM size) bytes of memory. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	size = Amount of memory to allocate. | ||||
| 	 * | ||||
| 	 * Returns: The pointer to the new allocated memory. | ||||
| 	 */ | ||||
|     void[] allocate(size_t size) @trusted nothrow | ||||
|     { | ||||
| 		immutable dataSize = addAlignment(size); | ||||
|  | ||||
| 		void* data = findBlock(dataSize); | ||||
| 		if (data is null) | ||||
| 		{ | ||||
| 			data = initializeRegion(dataSize); | ||||
| 		} | ||||
|  | ||||
| 		return data is null ? null : data[0..size]; | ||||
|     } | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto p = Ullocator.instance.allocate(20); | ||||
|  | ||||
| 		assert(p); | ||||
|  | ||||
| 		Ullocator.instance.deallocate(p); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Search for a block large enough to keep $(D_PARAM size) and split it | ||||
| 	 * into two blocks if the block is too large. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	size = Minimum size the block should have. | ||||
| 	 * | ||||
| 	 * Returns: Data the block points to or $(D_KEYWORD null). | ||||
| 	 */ | ||||
| 	private void* findBlock(size_t size) nothrow | ||||
| 	{ | ||||
| 		Block block1; | ||||
| 		RegionLoop: for (auto r = head; r !is null; r = r.next) | ||||
| 		{ | ||||
| 			block1 = cast(Block) (cast(void*) r + regionEntrySize); | ||||
| 			do | ||||
| 			{ | ||||
| 				if (block1.free && block1.size >= size) | ||||
| 				{ | ||||
| 					break RegionLoop; | ||||
| 				} | ||||
| 			} | ||||
| 			while ((block1 = block1.next) !is null); | ||||
| 		} | ||||
| 		if (block1 is null) | ||||
| 		{ | ||||
| 			return null; | ||||
| 		} | ||||
| 		else if (block1.size >= size + alignment + blockEntrySize) | ||||
| 		{ // Split the block if needed | ||||
| 			Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size); | ||||
| 			block2.prev = block1; | ||||
| 			if (block1.next is null) | ||||
| 			{ | ||||
| 				block2.next = null; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				block2.next = block1.next.next; | ||||
| 			} | ||||
| 			block1.next = block2; | ||||
|  | ||||
| 			block1.free = false; | ||||
| 			block2.free = true; | ||||
|  | ||||
| 			block2.size = block1.size - blockEntrySize - size; | ||||
| 			block1.size = size; | ||||
|  | ||||
| 			block2.region = block1.region; | ||||
| 			++block1.region.blocks; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			block1.free = false; | ||||
| 			++block1.region.blocks; | ||||
| 		} | ||||
| 		return cast(void*) block1 + blockEntrySize; | ||||
| 	} | ||||
|  | ||||
|     /** | ||||
| 	 * Deallocates a memory block. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	p = A pointer to the memory block to be freed. | ||||
| 	 * | ||||
| 	 * Returns: Whether the deallocation was successful. | ||||
| 	 */ | ||||
|     bool deallocate(void[] p) @trusted nothrow | ||||
|     { | ||||
| 		if (p is null) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		Block block = cast(Block) (p.ptr - blockEntrySize); | ||||
| 		if (block.region.blocks <= 1) | ||||
| 		{ | ||||
| 			if (block.region.prev !is null) | ||||
| 			{ | ||||
| 				block.region.prev.next = block.region.next; | ||||
| 			} | ||||
| 			else // Replace the list head. It is being deallocated | ||||
| 			{ | ||||
| 				head = block.region.next; | ||||
| 			} | ||||
| 			if (block.region.next !is null) | ||||
| 			{ | ||||
| 				block.region.next.prev = block.region.prev; | ||||
| 			} | ||||
| 			return munmap(block.region, block.region.size) == 0; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			block.free = true; | ||||
| 			--block.region.blocks; | ||||
| 			return true; | ||||
| 		} | ||||
|     } | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		auto p = Ullocator.instance.allocate(20); | ||||
|  | ||||
| 		assert(Ullocator.instance.deallocate(p)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Increases or decreases the size of a memory block. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	p    = A pointer to the memory block. | ||||
| 	 * 	size = Size of the reallocated block. | ||||
| 	 * | ||||
| 	 * Returns: Whether the reallocation was successful. | ||||
| 	 */ | ||||
| 	bool reallocate(ref void[] p, size_t size) @trusted nothrow | ||||
| 	{ | ||||
| 		if (size == p.length) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		auto reallocP = allocate(size); | ||||
| 		if (reallocP is null) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		if (p !is null) | ||||
| 		{ | ||||
| 			if (size > p.length) | ||||
| 			{ | ||||
| 				reallocP[0..p.length] = p[0..$]; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				reallocP[0..size] = p[0..size]; | ||||
| 			} | ||||
| 			deallocate(p); | ||||
| 		} | ||||
| 		p = reallocP; | ||||
|  | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		void[] p; | ||||
| 		Ullocator.instance.reallocate(p, 10 * int.sizeof); | ||||
| 		(cast(int[]) p)[7] = 123; | ||||
|  | ||||
| 		assert(p.length == 40); | ||||
|  | ||||
| 		Ullocator.instance.reallocate(p, 8 * int.sizeof); | ||||
|  | ||||
| 		assert(p.length == 32); | ||||
| 		assert((cast(int[]) p)[7] == 123); | ||||
|  | ||||
| 		Ullocator.instance.reallocate(p, 20 * int.sizeof); | ||||
| 		(cast(int[]) p)[15] = 8; | ||||
|  | ||||
| 		assert(p.length == 80); | ||||
| 		assert((cast(int[]) p)[15] == 8); | ||||
| 		assert((cast(int[]) p)[7] == 123); | ||||
|  | ||||
| 		Ullocator.instance.reallocate(p, 8 * int.sizeof); | ||||
|  | ||||
| 		assert(p.length == 32); | ||||
| 		assert((cast(int[]) p)[7] == 123); | ||||
|  | ||||
| 		Ullocator.instance.deallocate(p); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|      * Static allocator instance and initializer. | ||||
| 	 * | ||||
| 	 * Returns: The global $(D_PSYMBOL Allocator) instance. | ||||
| 	 */ | ||||
| 	static @property Ullocator instance() @trusted nothrow | ||||
| 	{ | ||||
| 		if (instance_ is null) | ||||
| 		{ | ||||
| 			immutable instanceSize = addAlignment(__traits(classInstanceSize, Ullocator)); | ||||
|  | ||||
| 			Region head; // Will become soon our region list head | ||||
| 			void* data = initializeRegion(instanceSize, head); | ||||
|  | ||||
| 			if (data is null) | ||||
| 			{ | ||||
| 				return null; | ||||
| 			} | ||||
| 			data[0..instanceSize] = typeid(Ullocator).initializer[]; | ||||
| 			instance_ = cast(Ullocator) data; | ||||
| 			instance_.head = head; | ||||
| 		} | ||||
| 		return instance_; | ||||
| 	} | ||||
|  | ||||
| 	/// | ||||
| 	unittest | ||||
| 	{ | ||||
| 		assert(instance is instance); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes a region for one element. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	size = Aligned size of the first data block in the region. | ||||
| 	 *  head = Region list head. | ||||
| 	 * | ||||
| 	 * Returns: A pointer to the data. | ||||
| 	 */ | ||||
| 	pragma(inline) | ||||
| 	private static void* initializeRegion(size_t size, | ||||
| 	                                      ref Region head) nothrow | ||||
| 	{ | ||||
| 		immutable regionSize = calculateRegionSize(size); | ||||
| 		void* p = mmap(null, | ||||
| 		               regionSize, | ||||
| 		               PROT_READ | PROT_WRITE, | ||||
| 		               MAP_PRIVATE | MAP_ANON, | ||||
| 		               -1, | ||||
| 		               0); | ||||
| 		if (p is MAP_FAILED) | ||||
| 		{ | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		Region region = cast(Region) p; | ||||
| 		region.blocks = 1; | ||||
| 		region.size = regionSize; | ||||
|  | ||||
| 		// Set the pointer to the head of the region list | ||||
| 		if (head !is null) | ||||
| 		{ | ||||
| 			head.prev = region; | ||||
| 		} | ||||
| 		region.next = head; | ||||
| 		region.prev = null; | ||||
| 		head = region; | ||||
|  | ||||
| 		// Initialize the data block | ||||
| 		void* memoryPointer = p + regionEntrySize; | ||||
| 		Block block1 = cast(Block) memoryPointer; | ||||
| 		block1.size = size; | ||||
| 		block1.free = false; | ||||
|  | ||||
| 		// It is what we want to return | ||||
| 		void* data = memoryPointer + blockEntrySize; | ||||
|  | ||||
| 		// Free block after data | ||||
| 		memoryPointer = data + size; | ||||
| 		Block block2 = cast(Block) memoryPointer; | ||||
| 		block1.prev = block2.next = null; | ||||
| 		block1.next = block2; | ||||
| 		block2.prev = block1; | ||||
| 		block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2; | ||||
| 		block2.free = true; | ||||
| 		block1.region = block2.region = region; | ||||
|  | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| 	/// Ditto. | ||||
| 	private void* initializeRegion(size_t size) nothrow | ||||
| 	{ | ||||
| 		return initializeRegion(size, head); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	x = Space to be aligned. | ||||
| 	 * | ||||
| 	 * Returns: Aligned size of $(D_PARAM x). | ||||
| 	 */ | ||||
| 	pragma(inline) | ||||
| 	private static immutable(size_t) addAlignment(size_t x) @safe pure nothrow | ||||
| 	out (result) | ||||
| 	{ | ||||
| 		assert(result > 0); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		return (x - 1) / alignment * alignment + alignment; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	x = Required space. | ||||
| 	 * | ||||
| 	 * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). | ||||
| 	 */ | ||||
| 	pragma(inline) | ||||
| 	private static immutable(size_t) calculateRegionSize(size_t x) | ||||
| 	@safe pure nothrow | ||||
| 	out (result) | ||||
| 	{ | ||||
| 		assert(result > 0); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		x += regionEntrySize + blockEntrySize * 2; | ||||
| 		return x / pageSize * pageSize + pageSize; | ||||
| 	} | ||||
|  | ||||
| 	enum alignment = 8; | ||||
|  | ||||
| 	private static Ullocator instance_; | ||||
|  | ||||
| 	private shared static immutable long pageSize; | ||||
|  | ||||
| 	private struct RegionEntry | ||||
| 	{ | ||||
| 		Region prev; | ||||
| 		Region next; | ||||
| 		uint blocks; | ||||
| 		ulong size; | ||||
| 	} | ||||
| 	private alias Region = RegionEntry*; | ||||
| 	private enum regionEntrySize = 32; | ||||
|  | ||||
| 	private Region head; | ||||
|  | ||||
| 	private struct BlockEntry | ||||
| 	{ | ||||
| 		Block prev; | ||||
| 		Block next; | ||||
| 		bool free; | ||||
| 		ulong size; | ||||
| 		Region region; | ||||
| 	} | ||||
| 	private alias Block = BlockEntry*; | ||||
| 	private enum blockEntrySize = 40; | ||||
| } | ||||
							
								
								
									
										324
									
								
								source/tanya/random.d
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								source/tanya/random.d
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| /** | ||||
|  * Copyright: Eugene Wissner 2016. | ||||
|  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, | ||||
|  *                  Mozilla Public License, v. 2.0). | ||||
|  * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) | ||||
|  */   | ||||
| module tanya.random; | ||||
|  | ||||
| import tanya.memory; | ||||
| import std.digest.sha; | ||||
| import std.typecons; | ||||
|  | ||||
| @nogc: | ||||
|  | ||||
| /// Block size of entropy accumulator (SHA-512). | ||||
| enum blockSize = 64; | ||||
|  | ||||
| /// Maximum amount gathered from the entropy sources. | ||||
| enum maxGather = 128; | ||||
|  | ||||
| /** | ||||
|  * Exception thrown if random number generating fails. | ||||
|  */ | ||||
| class EntropyException : Exception | ||||
| { | ||||
| @nogc: | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	msg  = Message to output. | ||||
| 	 * 	file = The file where the exception occurred. | ||||
| 	 * 	line = The line number where the exception occurred. | ||||
| 	 * 	next = The previous exception in the chain of exceptions, if any. | ||||
| 	 */ | ||||
| 	this(string msg, | ||||
| 	     string file = __FILE__, | ||||
| 	     size_t line = __LINE__, | ||||
| 	     Throwable next = null) pure @safe nothrow const | ||||
| 	{ | ||||
| 		super(msg, file, line, next); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Interface for implementing entropy sources. | ||||
|  */ | ||||
| abstract class EntropySource | ||||
| { | ||||
| @nogc: | ||||
| 	/// Amount of already generated entropy. | ||||
| 	protected ushort size_; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Minimum bytes required from the entropy source. | ||||
| 	 */ | ||||
| 	@property immutable(ubyte) threshold() const @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Whether this entropy source is strong. | ||||
| 	 */ | ||||
| 	@property immutable(bool) strong() const @safe pure nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Amount of already generated entropy. | ||||
| 	 */ | ||||
| 	@property ushort size() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return size_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 * 	size = Amount of already generated entropy. Cannot be smaller than the | ||||
| 	 * 	       already set value. | ||||
| 	 */ | ||||
| 	@property void size(ushort size) @safe pure nothrow | ||||
| 	{ | ||||
| 		size_ = size; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Poll the entropy source. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	output = Buffer to save the generate random sequence (the method will | ||||
| 	 * 	         to fill the buffer). | ||||
| 	 * | ||||
| 	 * Returns: Number of bytes that were copied to the $(D_PARAM output) | ||||
| 	 *          or $(D_PSYMBOL Nullable!ubyte.init) on error. | ||||
| 	 */ | ||||
| 	Nullable!ubyte poll(out ubyte[maxGather] output); | ||||
| } | ||||
|  | ||||
| version (linux) | ||||
| { | ||||
| 	extern (C) long syscall(long number, ...) nothrow; | ||||
|  | ||||
| 	/** | ||||
| 	 * Uses getrandom system call. | ||||
| 	 */ | ||||
| 	class PlatformEntropySource : EntropySource | ||||
| 	{ | ||||
| 	@nogc: | ||||
| 		/** | ||||
| 		 * Returns: Minimum bytes required from the entropy source. | ||||
| 		 */ | ||||
| 		override @property immutable(ubyte) threshold() const @safe pure nothrow | ||||
| 		{ | ||||
| 			return 32; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * Returns: Whether this entropy source is strong. | ||||
| 		 */ | ||||
| 		override @property immutable(bool) strong() const @safe pure nothrow | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * Poll the entropy source. | ||||
| 		 * | ||||
| 		 * Params: | ||||
| 		 * 	output = Buffer to save the generate random sequence (the method will | ||||
| 		 * 	         to fill the buffer). | ||||
| 		 * | ||||
| 		 * Returns: Number of bytes that were copied to the $(D_PARAM output) | ||||
| 		 *          or $(D_PSYMBOL Nullable!ubyte.init) on error. | ||||
| 		 */ | ||||
| 		override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow | ||||
| 		out (length) | ||||
| 		{ | ||||
| 			assert(length <= maxGather); | ||||
| 		} | ||||
| 		body | ||||
| 		{ | ||||
| 			// int getrandom(void *buf, size_t buflen, unsigned int flags); | ||||
| 			auto length = syscall(318, output.ptr, output.length, 0); | ||||
| 			Nullable!ubyte ret; | ||||
|  | ||||
| 			if (length >= 0) | ||||
| 			{ | ||||
| 				ret = cast(ubyte) length; | ||||
| 			} | ||||
| 			return ret; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Pseudorandom number generator. | ||||
|  * --- | ||||
|  * auto entropy = defaultAllocator.make!Entropy; | ||||
|  * | ||||
|  * ubyte[blockSize] output; | ||||
|  * | ||||
|  * output = entropy.random; | ||||
|  * | ||||
|  * defaultAllocator.finalize(entropy); | ||||
|  * --- | ||||
|  */ | ||||
| class Entropy | ||||
| { | ||||
| @nogc: | ||||
| 	/// Entropy sources. | ||||
| 	protected EntropySource[] sources; | ||||
|  | ||||
| 	private ubyte sourceCount_; | ||||
|  | ||||
| 	private Allocator allocator; | ||||
|  | ||||
| 	/// Entropy accumulator. | ||||
| 	protected SHA!(maxGather * 8, 512) accumulator; | ||||
|  | ||||
| 	/** | ||||
| 	 * Params: | ||||
| 	 *  maxSources = Maximum amount of entropy sources can be set. | ||||
| 	 * 	allocator  = Allocator to allocate entropy sources available on the | ||||
| 	 * 	             system. | ||||
| 	 */ | ||||
| 	this(size_t maxSources = 20, Allocator allocator = defaultAllocator) | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(maxSources > 0 && maxSources <= ubyte.max); | ||||
| 		assert(allocator !is null); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		allocator.resizeArray(sources, maxSources); | ||||
|  | ||||
| 		version (linux) | ||||
| 		{ | ||||
| 			this ~= allocator.make!PlatformEntropySource; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Amount of the registered entropy sources. | ||||
| 	 */ | ||||
| 	@property ubyte sourceCount() const @safe pure nothrow | ||||
| 	{ | ||||
| 		return sourceCount_; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Add an entropy source. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	source = Entropy source. | ||||
| 	 * | ||||
| 	 * Returns: $(D_PSYMBOL this). | ||||
| 	 * | ||||
| 	 * See_Also: | ||||
| 	 * 	$(D_PSYMBOL EntropySource) | ||||
| 	 */ | ||||
| 	Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow | ||||
| 		if (Op == "~") | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(sourceCount_ <= sources.length); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		sources[sourceCount_++] = source; | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns: Generated random sequence. | ||||
| 	 * | ||||
| 	 * Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was | ||||
| 	 *         registered or it failed. | ||||
| 	 */ | ||||
| 	@property ubyte[blockSize] random() | ||||
| 	in | ||||
| 	{ | ||||
| 		assert(sourceCount_ > 0, "No entropy sources defined."); | ||||
| 	} | ||||
| 	body | ||||
| 	{ | ||||
| 		bool haveStrong; | ||||
| 		ushort done; | ||||
| 		ubyte[blockSize] output; | ||||
|  | ||||
| 		do | ||||
| 		{ | ||||
| 			ubyte[maxGather] buffer; | ||||
|  | ||||
| 			// Run through our entropy sources | ||||
| 			for (ubyte i; i < sourceCount; ++i) | ||||
| 			{ | ||||
| 				auto outputLength = sources[i].poll(buffer); | ||||
|  | ||||
| 				if (!outputLength.isNull) | ||||
| 				{ | ||||
| 					if (outputLength > 0) | ||||
| 					{ | ||||
| 						update(i, buffer, outputLength); | ||||
| 						sources[i].size = cast(ushort) (sources[i].size + outputLength); | ||||
| 					} | ||||
| 					if (sources[i].size < sources[i].threshold) | ||||
| 					{ | ||||
| 						continue; | ||||
| 					} | ||||
| 					else if (sources[i].strong) | ||||
| 					{ | ||||
| 						haveStrong = true; | ||||
| 					} | ||||
| 				} | ||||
| 				done = 257; | ||||
| 			} | ||||
| 		} | ||||
| 		while (++done < 256); | ||||
|  | ||||
| 		if (!haveStrong) | ||||
| 		{ | ||||
| 			throw allocator.make!EntropyException("No strong entropy source defined."); | ||||
| 		} | ||||
|  | ||||
| 		output = accumulator.finish(); | ||||
|  | ||||
| 		// Reset accumulator and counters and recycle existing entropy | ||||
| 		accumulator.start(); | ||||
|  | ||||
| 		// Perform second SHA-512 on entropy | ||||
| 		output = sha512Of(output); | ||||
|  | ||||
| 		for (ubyte i = 0; i < sourceCount; ++i) | ||||
| 		{ | ||||
| 			sources[i].size = 0; | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Update entropy accumulator. | ||||
| 	 * | ||||
| 	 * Params: | ||||
| 	 * 	sourceId = Entropy source index in $(D_PSYMBOL sources). | ||||
| 	 * 	data     = Data got from the entropy source. | ||||
| 	 * 	length   = Length of the received data. | ||||
| 	 */ | ||||
| 	protected void update(in ubyte sourceId, | ||||
| 	                      ref ubyte[maxGather] data, | ||||
| 	                      ubyte length) @safe pure nothrow | ||||
| 	{ | ||||
| 		ubyte[2] header; | ||||
|  | ||||
| 		if (length > blockSize) | ||||
| 		{ | ||||
| 			data[0..64] = sha512Of(data); | ||||
| 			length = blockSize; | ||||
| 		} | ||||
|  | ||||
| 		header[0] = sourceId; | ||||
| 		header[1] = length; | ||||
|  | ||||
| 		accumulator.put(header); | ||||
| 		accumulator.put(data[0..length]); | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user