1 /**
2 Copyright: Copyright (c) 2017-2019 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module vox.utils.arena;
7 
8 import std.stdio;
9 import std.traits : isDynamicArray;
10 
11 ///
12 struct Arena(T)
13 {
14 	import vox.utils : alignValue, max, format, to, PAGE_SIZE, paddingSize;
15 	import std.range : isInputRange;
16 
17 	// Profiling stats:
18 	//    4k - 10.00% usage (12.9s)
19 	//   64k -  0.95% usage (11.6s)
20 	//  256k -  0.28% usage (11.5s)
21 	enum COMMIT_PAGE_SIZE = 65_536;
22 
23 	T* bufPtr;
24 	/// How many items can be stored without commiting more memory (committed items)
25 	size_t capacity;
26 	/// Number of items in the buffer. length <= capacity
27 	size_t length;
28 	/// Total reserved bytes
29 	size_t reservedBytes;
30 
31 	size_t committedBytes() { return alignValue(capacity * T.sizeof, COMMIT_PAGE_SIZE); }
32 
33 	uint uintLength() { return length.to!uint; }
34 	size_t byteLength() { return length * T.sizeof; }
35 	ref T opIndex(size_t at) { return bufPtr[at]; }
36 	ref T back() { return bufPtr[length-1]; }
37 	inout(T[]) data() inout { return bufPtr[0..length]; }
38 	bool empty() { return length == 0; }
39 	T* nextPtr() { return bufPtr + length; }
40 	bool contains(void* ptr) { return cast(void*)bufPtr <= ptr && cast(T*)ptr < cast(void*)(bufPtr + length); }
41 
42 	void setBuffer(ubyte[] reservedBuffer) {
43 		setBuffer(reservedBuffer, reservedBuffer.length);
44 	}
45 	void setBuffer(ubyte[] reservedBuffer, size_t committedBytes) {
46 		bufPtr = cast(T*)reservedBuffer.ptr;
47 		assert(bufPtr, "reservedBuffer is null");
48 		reservedBytes = reservedBuffer.length;
49 		// we can lose [0; T.sizeof-1] bytes here, need to round up to multiple of allocation size when committing
50 		capacity = committedBytes / T.sizeof;
51 		length = 0;
52 	}
53 	void clear() { length = 0; }
54 
55 	T[] put(T[] items ...) {
56 		size_t initialLength = length;
57 		version(Windows) {
58 			if (capacity - length < items.length) makeSpace(items.length);
59 		}
60 		//writefln("assign %X.%s @ %s..%s+%s cap %s", bufPtr, T.sizeof, length, length, items.length, capacity);
61 		bufPtr[length..length+items.length] = items;
62 		length += items.length;
63 		return bufPtr[initialLength..length];
64 	}
65 
66 	T[] put(R)(R itemRange) if (isInputRange!R) {
67 		size_t initialLength = length;
68 		foreach(item; itemRange)
69 			put(item);
70 		return bufPtr[initialLength..length];
71 	}
72 
73 	void stealthPut(T item) {
74 		version(Windows) {
75 			if (capacity == length) makeSpace(1);
76 		}
77 		bufPtr[length] = item;
78 	}
79 
80 	/// Increases length and returns void-initialized slice to be filled by user
81 	T[] voidPut(size_t howMany) {
82 		version(Windows) {
83 			if (capacity - length < howMany) makeSpace(howMany);
84 		}
85 		length += howMany;
86 		return bufPtr[length-howMany..length];
87 	}
88 
89 	void free(T[] array) {
90 		T* endPtr = bufPtr + length;
91 		T* arrayEndPtr = array.ptr + array.length;
92 		if (endPtr == arrayEndPtr) {
93 			length -= array.length;
94 		}
95 	}
96 
97 	void unput(size_t numItems) {
98 		length -= numItems;
99 	}
100 
101 	static if (is(T == ubyte))
102 	{
103 		void put(V)(auto ref V value) if (!isDynamicArray!V) {
104 			ubyte[] ptr = voidPut(V.sizeof);
105 			ptr[] = *cast(ubyte[V.sizeof]*)&value;
106 		}
107 
108 		void pad(size_t bytes) {
109 			voidPut(bytes)[] = 0;
110 		}
111 
112 		void padUntilAligned(size_t alignment) {
113 			pad(paddingSize(length, alignment));
114 		}
115 	}
116 
117 	version(Windows)
118 	void makeSpace(size_t items) {
119 		assert(items > (capacity - length));
120 		size_t _committedBytes = committedBytes;
121 		size_t bytesToCommit = alignValue((items - (capacity - length)) * T.sizeof, COMMIT_PAGE_SIZE);
122 		bytesToCommit = max(bytesToCommit, COMMIT_PAGE_SIZE);
123 
124 		if (_committedBytes + bytesToCommit > reservedBytes) {
125 			assert(false, format("Arena.makeSpace() out of memory: reserved %s, committed bytes %s, requested %s",
126 				reservedBytes, _committedBytes, bytesToCommit));
127 		}
128 
129 		version(Windows) {
130 			import vox.utils.windows : VirtualAlloc, MEM_COMMIT, PAGE_READWRITE;
131 			void* result = VirtualAlloc(cast(ubyte*)bufPtr + _committedBytes, bytesToCommit, MEM_COMMIT, PAGE_READWRITE);
132 			if (result is null) assert(false, "Cannot commit more bytes");
133 		} else version(Posix) {
134 			// noop, mmap already committed all memory
135 		} else static assert(false, "Not implemented on this platform");
136 
137 		capacity = (_committedBytes + bytesToCommit) / T.sizeof;
138 	}
139 }