1 /**
2 Copyright: Copyright (c) 2017-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.container.gapbuffer;
7 
8 import std.algorithm : max, equal;
9 import std.math : abs;
10 import std.stdio;
11 import std.experimental.allocator.gc_allocator;
12 alias allocator = GCAllocator.instance;
13 import voxelman.math : nextPOT;
14 import voxelman.container.chunkedrange;
15 
16 struct GapBuffer(T)
17 {
18 	private T* buffer;
19 	size_t length;
20 	alias opDollar = length;
21 	private size_t gapLength;
22 	private size_t gapStart;
23 
24 	private size_t secondChunkLength() const { return length - gapStart; }
25 	private size_t secondChunkStart() { return gapStart + gapLength; }
26 	private size_t capacity() { return gapLength + length; }
27 	private alias gapEnd = secondChunkStart;
28 
29 	void putAt(size_t index, const T[] items ...)
30 	{
31 		reserve(items.length);
32 		moveGapTo(index);
33 
34 		assert(index+items.length <= capacity);
35 		buffer[index..index+items.length] = items;
36 		length += items.length;
37 		gapStart += items.length;
38 		gapLength -= items.length;
39 		//printStructure();
40 	}
41 
42 	void put(const T[] items ...) { putAt(length, items); }
43 	alias putBack = put;
44 	void putFront(const T[] items ...) { putAt(0, items); }
45 
46 	void remove(size_t from, size_t itemsToRemove)
47 	{
48 		assert(from + itemsToRemove <= capacity);
49 		moveGapTo(from);
50 		length -= itemsToRemove;
51 		gapLength += itemsToRemove;
52 	}
53 	void removeFront(size_t itemsToRemove = 1) { remove(0, itemsToRemove); }
54 	void removeBack(size_t itemsToRemove = 1) { remove(length-itemsToRemove, itemsToRemove); }
55 
56 	void reserve(size_t items)
57 	{
58 		assert(cast(ptrdiff_t)(length - gapStart) >= 0); // invariant
59 		if (gapLength < items)
60 		{
61 			import core.memory;
62 			GC.removeRange(buffer);
63 			size_t newCapacity = nextPOT(capacity + items);
64 			void[] tmp = buffer[0..capacity];
65 			allocator.reallocate(tmp, newCapacity*T.sizeof);
66 			buffer = cast(T*)tmp.ptr;
67 			moveItems(secondChunkStart, newCapacity-secondChunkLength, secondChunkLength);
68 			gapLength = newCapacity - length;
69 			GC.addRange(buffer, capacity * T.sizeof, typeid(T));
70 		}
71 	}
72 
73 	void clear() nothrow
74 	{
75 		gapLength = capacity;
76 		length = 0;
77 		gapStart = 0;
78 	}
79 
80 	ref T front() { return this[0]; }
81 	ref T back() { return this[$-1]; }
82 
83 	ref T opIndex(size_t at)
84 	{
85 		assert(at < length);
86 		immutable size_t index = at < gapStart ? at : at + gapLength;
87 		return buffer[index];
88 	}
89 
90 	auto opSlice()
91 	{
92 		return GapBufferSlice!T(&this, 0, length);
93 	}
94 
95 	auto opSlice(size_t from, size_t to)
96 	{
97 		return this[][from..to];
98 	}
99 
100 	T[] getContinuousSlice(size_t from, size_t to)
101 	{
102 		moveGapTo(to);
103 		return buffer[from..to];
104 	}
105 
106 	private void moveItems(size_t from, size_t to, size_t length)
107 	{
108 		//writefln("  moveItems %s -> %s %s", from, to, length);
109 		if ( (to == from) || (length == 0) ) return;
110 
111 		if (from > to)
112 		{
113 			while(length > 0)
114 			{
115 				buffer[to++] = buffer[from++];
116 				--length;
117 			}
118 		}
119 		else
120 		{
121 			from += length;
122 			to += length;
123 
124 			while(length > 0)
125 			{
126 				buffer[--to] = buffer[--from];
127 				--length;
128 			}
129 		}
130 	}
131 
132 	private void moveGapTo(size_t newGapPos)
133 	{
134 		//writefln("moveGapTo %s -> %s %s", gapStart, newGapPos, gapLength);
135 		//printStructure();
136 		if (newGapPos < gapStart)
137 		{
138 			immutable size_t itemsToMove = gapStart - newGapPos;
139 			moveItems(newGapPos, gapEnd - itemsToMove, itemsToMove);
140 			gapStart = newGapPos;
141 		}
142 		else if (newGapPos > gapStart)
143 		{
144 			immutable size_t itemsToMove = newGapPos - gapStart;
145 			moveItems(secondChunkStart, gapStart, itemsToMove);
146 			gapStart = newGapPos;
147 		}
148 		//printStructure();
149 	}
150 
151 	private void printStructure()
152 	{
153 		writefln("  >%s|%s|%s", gapStart, gapLength, secondChunkLength);
154 		writefln("  >%(%s, %) | %(%s, %) | %(%s, %)", buffer[0..gapStart], buffer[gapStart..gapEnd], buffer[gapEnd..capacity]);
155 	}
156 
157 	int opApply(scope int delegate(ref T) dg)
158 	{
159 		int result = 0;
160 		foreach(ref v; buffer[0..gapStart])
161 			if (dg(v)) break;
162 		foreach(ref v; buffer[gapEnd..capacity])
163 			if (dg(v)) break;
164 		return result;
165 	}
166 
167 	int opApply(scope int delegate(size_t, ref T) dg)
168 	{
169 		int result = 0;
170 		size_t index;
171 		foreach(ref v; buffer[0..gapStart])
172 			if (dg(index++, v)) break;
173 		foreach(ref v; buffer[gapEnd..capacity])
174 			if (dg(index++, v)) break;
175 		return result;
176 	}
177 }
178 
179 struct GapBufferSlice(T)
180 {
181 	private GapBuffer!T* buf;
182 	size_t start;
183 	size_t length;
184 	alias opDollar = length;
185 
186 	bool empty() { return length == 0; }
187 	ref T front() { return (*buf)[start]; }
188 	ref T back() { return (*buf)[start+length-1]; }
189 	void popFront() { ++start; --length; }
190 	void popBack() { --length; }
191 	auto save() { return this; }
192 	ref T opIndex(size_t at) { return (*buf)[start + at]; }
193 
194 	auto opSlice(size_t from, size_t to)
195 	{
196 		assert(from <= length, "From must be less than length");
197 		assert(to <= length);
198 		assert(from <= to);
199 		immutable size_t len = to - from;
200 		return GapBufferSlice(buf, start+from, len);
201 	}
202 
203 	ChunkedRange!T toChunkedRange()
204 	{
205 		size_t end = start + length;
206 		if (start < buf.gapStart)
207 		{
208 			if (end >= buf.gapStart)
209 			{
210 				// slice contains the gap
211 				T[] first = buf.buffer[start..buf.gapStart];
212 				T* secondPtr = &buf.buffer[buf.gapEnd];
213 				size_t secondLength = length - (buf.gapStart-start);
214 				return ChunkedRange!T(first, secondLength, secondPtr, null, &GapBufferSlice_popFront!T);
215 			}
216 			else
217 			{
218 				// slice if before gap
219 				T[] first = buf.buffer[start..end];
220 				return ChunkedRange!T(first, 0, null, null, &GapBufferSlice_popFront!T);
221 			}
222 		}
223 		else
224 		{
225 			// slice if after gap
226 			size_t from = start + buf.gapLength;
227 			size_t to = from + length;
228 			T[] first = buf.buffer[from..to];
229 			return ChunkedRange!T(first, 0, null, null, &GapBufferSlice_popFront!T);
230 		}
231 	}
232 }
233 
234 private static void GapBufferSlice_popFront(T)(
235 	ref T[] front,
236 	ref size_t secondLength,
237 	ref void* secondPtr,
238 	ref void* unused)
239 {
240 	front = (cast(T*)secondPtr)[0..secondLength];
241 	secondPtr = null;
242 	secondLength = 0;
243 }
244 
245 unittest
246 {
247 	GapBuffer!char buf;
248 
249 	buf.put("ab");
250 	buf.remove(1, 1);
251 	assert(buf[].equal("a"));
252 	assert(buf.length == 1);
253 }
254 
255 unittest
256 {
257 	GapBuffer!int buf;
258 	assert(buf.length == 0);
259 	assert(buf.gapStart == 0);
260 	assert(buf.secondChunkLength == 0);
261 
262 	buf.put(1, 2, 3, 4);
263 	assert(buf.length == 4);
264 	assert(buf[].equal([1, 2, 3, 4]));
265 
266 	buf.putAt(2, 7, 8);
267 	assert(buf.length == 6);
268 	assert(buf[].equal([1, 2, 7, 8, 3, 4]));
269 
270 	buf.remove(2, 2);
271 	assert(buf.length == 4);
272 	assert(buf[].equal([1, 2, 3, 4]));
273 
274 	buf.remove(0, 4);
275 	assert(buf.length == 0);
276 	assert(buf[].equal((int[]).init));
277 }
278 
279 unittest
280 {
281 	GapBuffer!int buf;
282 	buf.putFront(1, 2, 3, 4);
283 	//writefln("%s\n", buf[]);
284 
285 	buf.putFront(1, 2, 3, 4);
286 	//writefln("%s\n", buf[]);
287 
288 	buf.putFront(1, 2, 3, 4);
289 	//writefln("%s\n", buf[]);
290 
291 	buf.putFront(1, 2, 3, 4);
292 	//writefln("%s\n", buf[]);
293 
294 	assert(buf[].equal([
295 		1, 2, 3, 4,
296 		1, 2, 3, 4,
297 		1, 2, 3, 4,
298 		1, 2, 3, 4,
299 		]));
300 
301 	buf.clear;
302 	assert(buf.length == 0);
303 }