1 module ikod.containers.compressedlist; 2 3 version(unittest) 4 {} 5 else 6 { 7 8 /* deprecated */ 9 10 private import core.memory; 11 private import core.bitop; 12 private import core.exception; 13 14 private import std.experimental.allocator; 15 private import std.experimental.allocator.mallocator : Mallocator; 16 private import std.experimental.allocator.gc_allocator; 17 private import std.experimental.logger; 18 private import std.format; 19 private import std.algorithm; 20 private import std.typecons; 21 22 private import automem: RefCounted, refCounted; 23 24 private import ikod.containers.internal; 25 26 private byte useFreePosition(ubyte[] m) @safe @nogc nothrow 27 { 28 import core.bitop: bsf; 29 // 30 // find free position, mark it as used and return it 31 // least significant bit in freeMap[0] means _nodes[0] 32 // most significant bit in freeMap[$-1] means nodes[$-1] 33 // 34 auto l = m.length; 35 for(uint i=0; i < l;i++) 36 { 37 ubyte v = m[i]; 38 if ( v < 255 ) 39 { 40 auto p = bsf(v ^ 0xff); 41 m[i] += 1 << p; 42 return cast(byte)((i<<3)+p); 43 } 44 } 45 assert(0); 46 } 47 private void markFreePosition(ubyte[] m, size_t position) @safe @nogc nothrow 48 { 49 auto p = position >> 3; 50 auto b = position & 0x7; 51 m[p] &= (1<<b)^0xff; 52 } 53 54 private bool isFreePosition(ubyte[] m, size_t position) @safe @nogc nothrow 55 { 56 auto p = position >> 3; 57 auto b = position & 0x7; 58 return (m[p] & (1<<b)) == 0; 59 } 60 private ubyte countBusy(ubyte[] m) @safe @nogc nothrow 61 { 62 import core.bitop; 63 ubyte s = 0; 64 foreach(b; m) 65 { 66 s+=popcnt(b); 67 } 68 return s; 69 } 70 @safe version(None) unittest 71 { 72 globalLogLevel = LogLevel.info; 73 import std.algorithm.comparison: equal; 74 ubyte[] map = [0,0]; 75 auto p = useFreePosition(map); 76 assert(p == 0, "expected 0, got %s".format(p)); 77 assert(map[0] == 1); 78 assert(!isFreePosition(map, 0)); 79 assert(isFreePosition(map, 1)); 80 81 p = useFreePosition(map); 82 assert(p == 1, "expected 1, got %s".format(p)); 83 map = [255,0]; 84 p = useFreePosition(map); 85 assert(p == 8, "expected 8, got %s".format(p)); 86 assert(map[1] == 1); 87 map = [255,0x01]; 88 p = useFreePosition(map); 89 assert(p == 9, "expected 9, got %s".format(p)); 90 assert(equal(map, [0xff, 0x03])); 91 markFreePosition(map, 8); 92 assert(equal(map, [0xff, 0x02]), "got %s".format(map)); 93 markFreePosition(map, 9); 94 assert(equal(map, [0xff, 0x00]), "got %s".format(map)); 95 markFreePosition(map, 0); 96 assert(equal(map, [0xfe, 0x00]), "got %s".format(map)); 97 } 98 99 /// 100 /// unrolled list with support only for: 101 /// 1. insert/delete front O(1) 102 /// 2. insert/delete back O(1) 103 /// 3. access/remove by index O(n/m), m - nodes per page 104 /// 105 deprecated("use unrolled list") 106 struct CompressedList(T, Allocator = Mallocator, bool GCRangesAllowed = true) 107 { 108 alias allocator = Allocator.instance; 109 alias StoredT = StoredType!T; 110 //enum MAGIC = 0x00160162; 111 enum PageSize = 512; // in bytes 112 enum NodesPerPage = PageSize/Node.sizeof; 113 static assert(NodesPerPage >= 1, "Node is too large to use this List, use DList instead"); 114 static assert(NodesPerPage <= 255, "Strange, but Node size is too small to use this List, use DList instead"); 115 116 enum BitMapLength = NodesPerPage % 8 ? NodesPerPage/8+1 : NodesPerPage/8; 117 118 119 struct Page { 120 /// 121 /// Page is fixed-length array of list Nodes 122 /// with batteries 123 ubyte[BitMapLength] _freeMap; 124 Page* _prevPage; 125 Page* _nextPage; 126 byte _firstNode; 127 byte _lastNode; 128 ubyte _count; // nodes counter 129 Node[NodesPerPage] _nodes; 130 } 131 132 struct Node { 133 StoredT v; 134 byte p; // prev index 135 byte n; // next index 136 } 137 138 struct Range { 139 private Page* page; 140 private byte index; 141 142 T front() @safe { 143 if ( page !is null && index == -1) 144 { 145 index = page._firstNode; 146 } 147 return page._nodes[index].v; 148 } 149 void popFront() @safe { 150 if ( page !is null && index == -1) 151 { 152 index = page._firstNode; 153 } 154 index = page._nodes[index].n; 155 if ( index != -1 ) 156 { 157 return; 158 } 159 page = page._nextPage; 160 if ( page is null ) 161 { 162 return; 163 } 164 index = page._firstNode; 165 } 166 bool empty() const @safe { 167 return page is null; 168 } 169 } 170 /// Iterator over items. 171 Range range() @safe { 172 return Range(_pages_first, -1); 173 } 174 private 175 { 176 Page* _pages_first, _pages_last; 177 ulong _length; 178 179 static Page* _freelist; 180 static int _freelist_len; 181 static enum _freelist_len_max = 100; 182 } 183 this(this) @safe 184 { 185 auto r = range(); 186 _pages_first = _pages_last =null; 187 _length = 0; 188 foreach(e; r) { 189 insertBack(e); 190 } 191 } 192 private void move_to_freelist(Page* page) @safe @nogc { 193 if ( _freelist_len >= _freelist_len_max ) 194 { 195 debug(cachetools) safe_tracef("dispose page"); 196 () @trusted { 197 static if ( UseGCRanges!(Allocator,T, GCRangesAllowed) ) { 198 GC.removeRange(&page._nodes[0]); 199 } 200 dispose(allocator, page); 201 }(); 202 return; 203 } 204 debug(cachetools) safe_tracef("put page in freelist"); 205 //page._epoque++; 206 page._nextPage = _freelist; 207 _freelist = page; 208 _freelist_len++; 209 } 210 211 private Page* peek_from_freelist() @safe { 212 if ( _freelist is null ) 213 { 214 Page* page = make!Page(allocator); 215 static if ( UseGCRanges!(Allocator, T, GCRangesAllowed) ) { 216 () @trusted { 217 GC.addRange(&page._nodes[0], Node.sizeof * NodesPerPage); 218 }(); 219 } 220 page._nextPage = page._prevPage = null; 221 page._firstNode = page._lastNode = -1; 222 return page; 223 } 224 Page* p = _freelist; 225 _freelist = p._nextPage; 226 _freelist_len--; 227 assert(_freelist_len>=0 && _freelist_len < _freelist_len_max); 228 p._nextPage = p._prevPage = null; 229 p._firstNode = p._lastNode = -1; 230 return p; 231 } 232 233 ~this() @safe { 234 clear(); 235 while(_freelist) 236 { 237 assert(_freelist_len>0); 238 auto next = _freelist._nextPage; 239 () @trusted { 240 static if ( UseGCRanges!(Allocator,T, GCRangesAllowed) ) { 241 GC.removeRange(&_freelist._nodes[0]); 242 } 243 dispose(allocator, _freelist); 244 }(); 245 _freelist_len--; 246 _freelist = next; 247 } 248 } 249 250 /// remove anything from list 251 void clear() @safe { 252 _length = 0; 253 Page* page = _pages_first, next; 254 while(page) 255 { 256 next = page._nextPage; 257 *page = Page(); 258 move_to_freelist(page); 259 page = next; 260 } 261 _length = 0; 262 _pages_first = _pages_last = null; 263 } 264 265 /// Is list empty? 266 bool empty() @safe const { 267 return _length == 0; 268 } 269 270 /// Items in the list. 271 ulong length() @safe const { 272 return _length; 273 } 274 275 // /// remove node (by 'Pointer') 276 // void remove(ref NodePointer p) @system { 277 // if ( empty ) 278 // { 279 // assert(0, "Tried to remove from empty list"); 280 // } 281 // _length--; 282 // Page *page = p._page; 283 // byte index = p._index; 284 // assert(!isFreePosition(page._freeMap, index), "you tried to remove already free list element"); 285 // with (page) 286 // { 287 // assert(_count>0); 288 // _count--; 289 // // unlink from list 290 // auto next = _nodes[index].n; 291 // auto prev = _nodes[index].p; 292 // if ( prev >= 0) 293 // { 294 // _nodes[prev].n = next; 295 // } 296 // else 297 // { 298 // _firstNode = next; 299 // } 300 // if ( next >= 0) 301 // { 302 // _nodes[next].p = prev; 303 // } 304 // else 305 // { 306 // _lastNode = prev; 307 // } 308 // //_nodes[index].n = _nodes[index].p = -1; 309 // markFreePosition(_freeMap, index); 310 // } 311 // if ( page._count == 0 ) 312 // { 313 // // relase this page 314 // if ( _pages_first == page ) 315 // { 316 // assert(page._prevPage is null); 317 // _pages_first = page._nextPage; 318 // } 319 // if ( _pages_last == page ) 320 // { 321 // assert(page._nextPage is null); 322 // _pages_last = page._prevPage; 323 // } 324 // if ( page._nextPage !is null ) 325 // { 326 // page._nextPage._prevPage = page._prevPage; 327 // } 328 // if ( page._prevPage !is null ) 329 // { 330 // page._prevPage._nextPage = page._nextPage; 331 // } 332 // move_to_freelist(page); 333 // } 334 // // at this point page can be disposed 335 // } 336 337 /// List front item 338 T front() @safe { 339 if ( empty ) 340 { 341 assert(0, "Tried to access front of empty list"); 342 } 343 Page* p = _pages_first; 344 assert( p !is null); 345 assert( p._count > 0 ); 346 with(p) 347 { 348 return _nodes[_firstNode].v; 349 } 350 } 351 352 /// Pop front item 353 void popFront() @safe { 354 if ( empty ) 355 { 356 assert(0, "Tried to popFront from empty list"); 357 } 358 _length--; 359 Page* page = _pages_first; 360 //debug(cachetools) safe_tracef("popfront: page before: %s", *page); 361 assert(page !is null); 362 with (page) { 363 assert(_count>0); 364 assert(!isFreePosition(_freeMap, _firstNode)); 365 auto first = _firstNode; 366 auto next = _nodes[first].n; 367 markFreePosition(_freeMap, first); 368 if ( next >= 0 ) 369 { 370 _nodes[next].p = -1; 371 } 372 //_nodes[first].n = _nodes[first].p = -1; 373 _count--; 374 _firstNode = next; 375 } 376 if ( page._count == 0 ) 377 { 378 // relase this page 379 _pages_first = page._nextPage; 380 move_to_freelist(page); 381 if ( _pages_first is null ) 382 { 383 _pages_last = null; 384 } 385 else 386 { 387 _pages_first._prevPage = null; 388 } 389 } 390 debug(cachetools) safe_tracef("popfront: page after: %s", *page); 391 } 392 393 /// Insert item at front. 394 void insertFront(T v) { 395 _length++; 396 Page* page = _pages_first; 397 if ( page is null ) 398 { 399 page = peek_from_freelist(); 400 _pages_first = _pages_last = page; 401 } 402 if (page._count == NodesPerPage) 403 { 404 Page* new_page = peek_from_freelist(); 405 new_page._nextPage = page; 406 page._prevPage = new_page; 407 _pages_first = new_page; 408 page = new_page; 409 } 410 // there is free space 411 auto index = useFreePosition(page._freeMap); 412 assert(index < NodesPerPage); 413 Node nn = Node(v, -1, page._firstNode); 414 move(nn, page._nodes[index]); 415 if (page._count == 0) 416 { 417 page._firstNode = page._lastNode = cast(ubyte)index; 418 } 419 else 420 { 421 assert(page._firstNode >= 0); 422 assert(!isFreePosition(page._freeMap, page._firstNode)); 423 page._nodes[page._firstNode].p = cast(ubyte)index; 424 page._firstNode = cast(ubyte)index; 425 } 426 page._count++; 427 assert(page._count == countBusy(page._freeMap)); 428 debug(cachetools) safe_tracef("page after insert front: %s", *page); 429 return; 430 } 431 432 /// List back item. 433 T back() @safe { 434 if ( empty ) 435 { 436 assert(0, "Tried to access back of empty list"); 437 } 438 Page* p = _pages_last; 439 assert( p !is null); 440 assert( p._count > 0 ); 441 //debug(cachetools) safe_tracef("page: %s", *p); 442 with(p) 443 { 444 return _nodes[_lastNode].v; 445 } 446 } 447 448 /// Pop back item from list. 449 void popBack() @safe { 450 if ( empty ) 451 { 452 assert(0, "Tried to popBack from empty list"); 453 } 454 _length--; 455 Page* page = _pages_last; 456 assert(page !is null); 457 with (page) { 458 assert(_count>0); 459 assert(!isFreePosition(_freeMap, _lastNode)); 460 auto last = _lastNode; 461 auto prev = _nodes[last].p; 462 markFreePosition(_freeMap, last); 463 if ( prev >=0 ) 464 { 465 _nodes[prev].n = -1; 466 } 467 //_nodes[last].n = _nodes[last].p = -1; 468 _count--; 469 _lastNode = prev; 470 } 471 if ( page._count == 0 ) 472 { 473 debug(cachetools) safe_tracef("release page"); 474 // relase this page 475 _pages_last = page._prevPage; 476 move_to_freelist(page); 477 if ( _pages_last is null ) 478 { 479 _pages_first = null; 480 } 481 else 482 { 483 _pages_last._nextPage = null; 484 } 485 } 486 } 487 488 /// Insert item back. 489 void insertBack(T v) { 490 _length++; 491 Page* page = _pages_last; 492 if ( page is null ) 493 { 494 page = peek_from_freelist(); 495 _pages_first = _pages_last = page; 496 } 497 if (page._count == NodesPerPage) 498 { 499 Page* new_page = peek_from_freelist(); 500 new_page._prevPage = page; 501 page._nextPage = new_page; 502 _pages_last = new_page; 503 page = new_page; 504 } 505 // there is free space 506 auto index = useFreePosition(page._freeMap); 507 assert(index < NodesPerPage); 508 Node nn = Node(v, page._lastNode, -1); 509 move(nn, page._nodes[index]); 510 if (page._count == 0) 511 { 512 page._firstNode = page._lastNode = cast(ubyte)index; 513 } 514 else 515 { 516 assert(page._lastNode >= 0); 517 assert(!isFreePosition(page._freeMap, page._lastNode)); 518 page._nodes[page._lastNode].n = cast(ubyte)index; 519 page._lastNode = cast(ubyte)index; 520 } 521 page._count++; 522 assert(page._count == countBusy(page._freeMap)); 523 //debug(cachetools) safe_tracef("page: %s", *page); 524 return; 525 } 526 } 527 528 /// 529 @safe version(None_Deprecated) unittest 530 { 531 import std.experimental.logger; 532 import std.algorithm; 533 import std.range; 534 535 globalLogLevel = LogLevel.info; 536 CompressedList!int list; 537 foreach(i;0..66) 538 { 539 list.insertFront(i); 540 assert(list.front == i); 541 } 542 assert(list.length == 66); 543 assert(list.back == 0); 544 list.popFront(); 545 assert(list.length == 65); 546 assert(list.front == 64); 547 list.popFront(); 548 assert(list.length == 64); 549 assert(list.front == 63); 550 while( !list.empty ) 551 { 552 list.popFront(); 553 } 554 foreach(i;1..19) 555 { 556 list.insertFront(i); 557 assert(list.front == i); 558 } 559 foreach(i;1..19) 560 { 561 assert(list.back == i); 562 assert(list.length == 19-i); 563 list.popBack(); 564 } 565 assert(list.empty); 566 list.insertBack(99); 567 assert(list.front == 99); 568 assert(list.back == 99); 569 list.insertBack(100); 570 assert(list.front == 99); 571 assert(list.back == 100); 572 list.insertFront(98); 573 list.insertBack(101); 574 () @trusted // * and remove for poiners is unsafe 575 { 576 list.clear(); 577 assert(list.empty); 578 579 foreach(i;0..1000) 580 { 581 list.insertBack(i); 582 } 583 assert(equal(list.range(), iota(0,1000))); 584 list.clear(); 585 586 assert(list.empty); 587 iota(0, 1000).each!(i => list.insertBack(i)); 588 auto r = list.range(); 589 while(!r.empty) 590 { 591 int v = r.front; 592 r.popFront(); 593 } 594 assert(list.length == 1000, "expected empty list, got length %d".format(list.length)); 595 }(); 596 597 () @nogc 598 { 599 struct S {} 600 CompressedList!(immutable S) islist; 601 immutable S s = S(); 602 islist.insertFront(s); 603 }(); 604 class C 605 { 606 int c; 607 this(int v) { 608 c = v; 609 } 610 } 611 CompressedList!C clist; 612 foreach(i;0..5000) 613 { 614 clist.insertBack(new C(i)); 615 } 616 foreach(i;0..4500) 617 { 618 clist.popBack(); 619 } 620 assert(clist.length == 500); 621 clist.clear(); 622 } 623 624 // unittest for unsafe types 625 version(None_Deprecated) unittest { 626 import std.variant; 627 alias T = Algebraic!(int, string); 628 auto v = T(1); 629 CompressedList!T cl; 630 cl.insertFront(v); 631 assert(cl.front == v); 632 cl.insertBack(v); 633 cl.popFront; 634 cl.popBack; 635 } 636 637 @safe @nogc version(None_Deprecated) unittest { 638 import std.range, std.algorithm; 639 CompressedList!int a, b; 640 iota(0,100).each!(e => a.insertBack(e)); 641 a.popFront(); 642 b = a; 643 assert(equal(a, b)); 644 } 645 646 @("range") 647 @safe @nogc version(None_Deprecated) unittest { 648 import std.range, std.algorithm; 649 CompressedList!int a; 650 iota(0,100).each!(e => a.insertBack(e)); 651 assert(equal(a.range(), iota(0,100))); 652 } 653 }