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 }