1 /// Reference counting module 2 module gfx.core.rc; 3 4 /// definition of how the gfx package itself implements reference counting 5 version(GfxRcAtomic) 6 { 7 alias GfxRefCounted = AtomicRefCounted; 8 enum gfxRcCode = atomicRcCode; 9 } 10 else version(GfxRcMixed) 11 { 12 alias GfxRefCounted = AtomicRefCounted; 13 enum gfxRcCode = mixedRcCode; 14 } 15 else { 16 alias GfxRefCounted = RefCounted; 17 enum gfxRcCode = rcCode; 18 } 19 20 21 /// A resource that can be disposed 22 interface Disposable 23 { 24 /// Dispose the underlying resource 25 void dispose(); 26 } 27 28 /// A non-atomic reference counted resource. 29 /// Objects implementing this interface should have exterior locking if used 30 /// as shared. 31 interface RefCounted : Disposable 32 { 33 /// The number of active references 34 @property size_t refCount() const; 35 36 /// Increment the reference count. 37 void retain(); 38 39 /// Decrement the reference count and dispose if it reaches zero. 40 void release() 41 in { assert(refCount > 0); } 42 43 /// Get a copy of this RefCounted instance if the refCount >= 1. 44 /// This increases the refCount by 1. This should only be used to keep 45 /// weak reference and ensures that the resource is not disposed. 46 RefCounted rcLock() 47 out(res) { assert((res && refCount >= 2) || (!res && !refCount)); } 48 49 override void dispose() 50 in { assert(refCount == 0); } // add this additional contract 51 } 52 53 /// A atomic reference counted resource. 54 /// Objects implementing this interface can be safely used as shared. 55 /// 56 /// Important note: AtomicRefCounted extends RefCounted (non-atomic). 57 /// If it is implemented using mixedRcCode, then the following statements apply 58 /// - When the object is shared, the atomic reference count is used. 59 /// - When the object is not shared, the non-atomic reference count is used. 60 /// 61 /// Although this sounds neat, it makes certain things quite difficult to implement 62 /// Eg. having some rc in a parallel loop. (parallel spawns threads without any guard or lock 63 /// - see the gfx-d:shadow example) 64 /// For that reason, the atomicRcCode enum is available that implements both 65 /// RefCounted and AtomicRefCounted atomically. 66 interface AtomicRefCounted : RefCounted 67 { 68 /// Atomically load the number of active references. 69 shared @property size_t refCount() const; 70 71 /// Atomically increment the reference count. 72 shared void retain(); 73 74 /// Atomically decrement the reference count and dispose if it reaches zero. 75 /// The object is locked using its own mutex when dispose is called. 76 shared void release() 77 in { assert(refCount > 0); } 78 79 /// Get a copy of this RefCounted instance if the refCount >= 1. 80 /// This increases the refCount by 1. This should only be used to keep 81 /// weak reference and ensures that the resource is not disposed. 82 /// The operation is atomic. 83 shared shared(AtomicRefCounted) rcLock(); 84 // out(res) { 85 // assert((res && this.refCount >= 1) || (!res && !this.refCount)); 86 // } 87 // this contract create a failure on ldc2: 88 // cannot implicitely convert shared(T) to const(AtomicRefCounted) 89 } 90 91 /// compile time check that T can be ref counted non-atomically. 92 enum isNonAtomicRefCounted(T) = is(T : RefCounted) && !is(T == shared); 93 /// compile time check that T can be ref counted atomically. 94 enum isAtomicRefCounted(T) = is(T : shared(AtomicRefCounted)); 95 /// compile time check that T can be ref counted (either shared or not) 96 enum isRefCounted(T) = isNonAtomicRefCounted!T || isAtomicRefCounted!T; 97 98 99 /// A string that can be mixed-in a class declaration to implement RefCounted. 100 enum rcCode = nonSharedNonAtomicMethods ~ q{ 101 private size_t _refCount=0; 102 }; 103 104 /// A string that can be mixed-in a class declaration to implement AtomicRefCounted. 105 /// The RefCounted methods (non-shared) are implemented without atomicity. 106 /// The AtomicRefCounted methods (shared) are implemented atomically. 107 enum mixedRcCode = nonSharedNonAtomicMethods ~ sharedAtomicMethods ~ q{ 108 private size_t _refCount=0; 109 }; 110 111 /// A string that can be mixed-in a class declaration to implement AtomicRefCounted. 112 /// Both shared and non-shared methods are implemented atomically. 113 /// This is useful for things such as a parallel loop 114 enum atomicRcCode = nonSharedAtomicMethods ~ sharedAtomicMethods ~ q{ 115 private shared size_t _refCount=0; 116 }; 117 118 /// Dispose GC allocated array of resources 119 void disposeArray(T)(ref T[] arr) if (is(T : Disposable) && !isRefCounted!T) 120 { 121 import std.algorithm : each; 122 arr.each!(el => el.dispose()); 123 arr = null; 124 } 125 126 /// Dispose GC allocated associative array of resources 127 void disposeArray(T, K)(ref T[K] arr) if (is(T : Disposable) && !isRefCounted!T) 128 { 129 import std.algorithm : each; 130 arr.each!((k, el) { el.dispose(); }); 131 arr = null; 132 } 133 134 /// Cast hack to get around a bug in DMD front-end. 135 /// Cast non shared atomic rc interfaces when calling retain or release. 136 /// See https://issues.dlang.org/show_bug.cgi?id=18138 137 template RcHack(T) if (isRefCounted!T) { 138 static if (is(T == interface) && is(T : AtomicRefCounted) && !is(T == shared)) { 139 alias RcHack = RefCounted; 140 } 141 else { 142 alias RcHack = T; 143 } 144 } 145 146 /// Retain GC allocated array of ref-counted resources 147 void retainArray(T)(ref T[] arr) if (isRefCounted!T) 148 { 149 import std.algorithm : each; 150 arr.each!(el => (cast(RcHack!T)el).retain()); 151 } 152 /// Retain GC allocated associative array of ref-counted resources 153 void retainArray(T, K)(ref T[K] arr) if (isRefCounted!T) 154 { 155 import std.algorithm : each; 156 arr.each!((k, el) { (cast(RcHack!T)el).retain(); }); 157 } 158 159 /// Release GC allocated array of ref-counted resources 160 void releaseArray(T)(ref T[] arr) if (isRefCounted!T) 161 { 162 import std.algorithm : each; 163 arr.each!(el => (cast(RcHack!T)el).release()); 164 arr = null; 165 } 166 /// Release GC allocated associative array of ref-counted resources 167 void releaseArray(T, K)(ref T[K] arr) if (isRefCounted!T) 168 { 169 import std.algorithm : each; 170 arr.each!((k, el) { (cast(RcHack!T)el).release(); }); 171 arr = null; 172 } 173 174 /// Reinitialises a GC allocated array of struct. 175 /// Useful if the struct release resource in its destructor. 176 void reinitArray(T)(ref T[] arr) if (is(T == struct)) 177 { 178 foreach(ref t; arr) { 179 t = T.init; 180 } 181 arr = null; 182 } 183 /// Reinitialises a GC allocated associative array of struct. 184 /// Useful if the struct release resource in its destructor. 185 void reinitArray(T, K)(ref T[K] arr) if (is(T == struct)) 186 { 187 foreach(k, ref t; arr) { 188 t = T.init; 189 } 190 arr = null; 191 } 192 193 /// Helper that build a new instance of T and returns it within a Rc!T 194 template makeRc(T) if (isRefCounted!T) 195 { 196 Rc!T makeRc(Args...)(Args args) 197 { 198 return Rc!T(new T(args)); 199 } 200 } 201 202 /// Helper that places an instance of T within a Rc!T 203 template rc(T) if (isRefCounted!T) 204 { 205 Rc!T rc(T obj) 206 { 207 if (obj) { 208 return Rc!T(obj); 209 } 210 else { 211 return (Rc!T).init; 212 } 213 } 214 } 215 216 @property Rc!T nullRc(T)() if (isRefCounted!T) { 217 return Rc!T.init; 218 } 219 220 /// Helper struct that manages the reference count of an object using RAII. 221 struct Rc(T) if (isRefCounted!T) 222 { 223 private T _obj; 224 225 private alias HackT = RcHack!T; 226 227 /// Build a Rc instance with the provided resource 228 this(T obj) 229 { 230 _obj = obj; 231 if (obj) { 232 (cast(HackT)_obj).retain(); 233 } 234 } 235 236 /// Postblit adds a reference to the held reference. 237 this(this) 238 { 239 if (_obj) (cast(HackT)_obj).retain(); 240 } 241 242 /// Removes a reference to the held reference. 243 ~this() 244 { 245 if(_obj) (cast(HackT)_obj).release(); 246 } 247 248 /// Assign another resource. Release the previously held ref and retain the new one. 249 void opAssign(T obj) 250 { 251 if(_obj) (cast(HackT)_obj).release(); 252 _obj = obj; 253 if(_obj) (cast(HackT)_obj).retain(); 254 } 255 256 /// Check whether this Rc is assigned to a resource. 257 bool opCast(T : bool)() const 258 { 259 return loaded; 260 } 261 262 /// Check whether this Rc is assigned to a resource. 263 @property bool loaded() const 264 { 265 return _obj !is null; 266 } 267 268 /// Reset the resource. 269 void unload() 270 { 271 if(_obj) { 272 (cast(HackT)_obj).release(); 273 _obj = null; 274 } 275 } 276 277 /// Access to the held resource. 278 @property inout(T) obj() inout { return _obj; } 279 280 alias obj this; 281 } 282 283 /// Helper struct that keeps a weak reference to a Resource. 284 struct Weak(T) if (is(T : Disposable)) 285 { 286 private T _obj; 287 static if (is(T == interface) && is(T : AtomicRefCounted) && !is(T == shared)) { 288 // see https://issues.dlang.org/show_bug.cgi?id=18138 289 private alias HackT = RefCounted; 290 } 291 else { 292 private alias HackT = T; 293 } 294 295 /// Build a Weak instance. 296 this(T obj) 297 { 298 _obj = obj; 299 } 300 301 /// Reset the internal reference. 302 void reset() 303 { 304 _obj = null; 305 } 306 307 /// Check whether the resource has been disposed. 308 @property bool disposed() const 309 { 310 return !_obj || (_obj.refCount == 0); 311 } 312 313 /// Return a Rc that contain the underlying resource if it has not been disposed. 314 Rc!T lock() 315 { 316 Rc!T rc; 317 rc._obj = _obj ? cast(T)((cast(HackT)_obj).rcLock()) : null; 318 if (!rc._obj) _obj = null; 319 return rc; 320 } 321 } 322 323 private enum nonSharedNonAtomicMethods = q{ 324 325 public final override @property size_t refCount() const { return _refCount; } 326 327 public final override void retain() 328 { 329 _refCount += 1; 330 debug(rc) { 331 import std.experimental.logger : tracef; 332 tracef("retain %s: %s", typeof(this).stringof, refCount); 333 } 334 } 335 336 public final override void release() 337 { 338 _refCount -= 1; 339 debug(rc) { 340 import std.experimental.logger : tracef; 341 tracef("release %s: %s", typeof(this).stringof, refCount); 342 } 343 if (!_refCount) { 344 debug(rc) { 345 import std.experimental.logger : tracef; 346 tracef("dispose %s", typeof(this).stringof); 347 } 348 dispose(); 349 } 350 } 351 352 public final override typeof(this) rcLock() 353 { 354 if (_refCount) { 355 ++_refCount; 356 debug(rc) { 357 import std.experimental.logger : tracef; 358 tracef("rcLock %s: %s", typeof(this).stringof, _refCount); 359 } 360 return this; 361 } 362 else { 363 debug(rc) { 364 import std.experimental.logger : tracef; 365 tracef("rcLock %s: %s", typeof(this).stringof, _refCount); 366 } 367 return null; 368 } 369 } 370 }; 371 372 private enum nonSharedAtomicMethods = q{ 373 374 public final override @property size_t refCount() const 375 { 376 import core.atomic : atomicLoad; 377 return atomicLoad(_refCount); 378 } 379 380 public final override void retain() 381 { 382 import core.atomic : atomicOp; 383 immutable rc = atomicOp!"+="(_refCount, 1); 384 debug(rc) { 385 import std.experimental.logger : logf; 386 logf("retain %s: %s", typeof(this).stringof, rc); 387 } 388 } 389 390 public final override void release() 391 { 392 import core.atomic : atomicOp; 393 immutable rc = atomicOp!"-="(_refCount, 1); 394 395 debug(rc) { 396 import std.experimental.logger : logf; 397 logf("release %s: %s", typeof(this).stringof, rc); 398 } 399 if (rc == 0) { 400 debug(rc) { 401 import std.experimental.logger : logf; 402 logf("dispose %s", typeof(this).stringof); 403 } 404 synchronized(this) { 405 this.dispose(); 406 } 407 } 408 } 409 410 public final override typeof(this) rcLock() 411 { 412 import core.atomic : atomicLoad, cas; 413 while (1) { 414 immutable c = atomicLoad(_refCount); 415 416 if (c == 0) { 417 debug(rc) { 418 logf("rcLock %s: %s", typeof(this).stringof, c); 419 } 420 return null; 421 } 422 if (cas(&_refCount, c, c+1)) { 423 debug(rc) { 424 logf("rcLock %s: %s", typeof(this).stringof, c+1); 425 } 426 return this; 427 } 428 } 429 } 430 }; 431 432 private enum sharedAtomicMethods = q{ 433 434 public final shared override @property size_t refCount() const 435 { 436 import core.atomic : atomicLoad; 437 return atomicLoad(_refCount); 438 } 439 440 public final shared override void retain() 441 { 442 import core.atomic : atomicOp; 443 immutable rc = atomicOp!"+="(_refCount, 1); 444 debug(rc) { 445 import std.experimental.logger : logf; 446 logf("retain %s: %s", typeof(this).stringof, rc); 447 } 448 } 449 450 public final shared override void release() 451 { 452 import core.atomic : atomicOp; 453 immutable rc = atomicOp!"-="(_refCount, 1); 454 455 debug(rc) { 456 import std.experimental.logger : logf; 457 logf("release %s: %s", typeof(this).stringof, rc); 458 } 459 if (rc == 0) { 460 debug(rc) { 461 import std.experimental.logger : logf; 462 logf("dispose %s", typeof(this).stringof); 463 } 464 synchronized(this) { 465 import std.traits : Unqual; 466 // cast shared away 467 auto obj = cast(Unqual!(typeof(this)))this; 468 obj.dispose(); 469 } 470 } 471 } 472 473 public final shared override shared(typeof(this)) rcLock() 474 { 475 import core.atomic : atomicLoad, cas; 476 while (1) { 477 immutable c = atomicLoad(_refCount); 478 479 if (c == 0) { 480 debug(rc) { 481 logf("rcLock %s: %s", typeof(this).stringof, c); 482 } 483 return null; 484 } 485 if (cas(&_refCount, c, c+1)) { 486 debug(rc) { 487 logf("rcLock %s: %s", typeof(this).stringof, c+1); 488 } 489 return this; 490 } 491 } 492 } 493 }; 494 495 // private string numberizeCodeLines(string code) { 496 // import std.string : splitLines; 497 // import std.format : format; 498 // string res; 499 // auto lines = code.splitLines(); 500 // foreach (i, l; lines) { 501 // res ~= format("%s. %s\n", i+1, l); 502 // } 503 // return res; 504 // } 505 506 // pragma(msg, numberizeCodeLines(atomicRcCode)); 507 508 // using version(unittest) instead of private creates link errors 509 // in test builds in apps/libs depending on gfx-d (??) 510 private 511 { 512 int rcCount = 0; 513 int structCount = 0; 514 515 class RcClass : RefCounted 516 { 517 mixin(rcCode); 518 519 this() 520 { 521 rcCount += 1; 522 } 523 524 override void dispose() 525 { 526 rcCount -= 1; 527 } 528 } 529 530 struct RcStruct 531 { 532 Rc!RcClass obj; 533 } 534 535 struct RcArrStruct 536 { 537 Rc!RcClass[] objs; 538 539 ~this() 540 { 541 foreach(ref o; objs) { 542 o = Rc!RcClass.init; 543 } 544 } 545 } 546 547 struct RcArrIndStruct 548 { 549 RcStruct[] objs; 550 551 ~this() 552 { 553 foreach(ref o; objs) { 554 o = RcStruct.init; 555 } 556 } 557 } 558 559 560 unittest 561 { 562 { 563 auto arr = RcArrStruct([makeRc!RcClass(), makeRc!RcClass()]); 564 assert(rcCount == 2); 565 foreach(obj; arr.objs) { 566 assert(rcCount == 2); 567 } 568 assert(rcCount == 2); 569 } 570 assert(rcCount == 0); 571 } 572 573 574 unittest 575 { 576 { 577 auto obj = makeRc!RcClass(); 578 assert(rcCount == 1); 579 } 580 assert(rcCount == 0); 581 } 582 583 unittest 584 { 585 { 586 auto obj = RcStruct(makeRc!RcClass()); 587 assert(rcCount == 1); 588 } 589 assert(rcCount == 0); 590 } 591 592 unittest 593 { 594 Weak!RcClass weak; 595 { 596 auto locked = weak.lock(); 597 assert(!locked.loaded); 598 } 599 { 600 auto rc = makeRc!RcClass(); 601 assert(rcCount == 1); 602 assert(rc.refCount == 1); 603 weak = Weak!RcClass(rc); 604 assert(rc.refCount == 1); 605 { 606 auto locked = weak.lock(); 607 assert(locked.loaded); 608 assert(rc.refCount == 2); 609 } 610 assert(rc.refCount == 1); 611 } 612 assert(rcCount == 0); 613 assert(weak.disposed); 614 { 615 auto locked = weak.lock(); 616 assert(!locked.loaded); 617 } 618 } 619 } 620 621 static if(false) 622 { 623 /// Creates a new instance of $(D T) and returns it under a $(D Uniq!T). 624 template makeUniq(T) 625 if (is(T : Disposable)) 626 { 627 Uniq!T makeUniq(Args...)(Args args) 628 { 629 return Uniq!T(new T (args)); 630 } 631 } 632 633 debug(Uniq) 634 { 635 import std.stdio; 636 } 637 638 /// A helper struct that manage the lifetime of a Disposable using RAII. 639 /// Note: dlang has capability to enforce a parameter be a lvalue (ref param) 640 /// but has no mechanism such as c++ rvalue reference which would enforce 641 /// true uniqueness by the compiler. Uniq gives additional robustness, but it is 642 /// up to the programmer to make sure that the values passed in by rvalue are 643 /// not referenced somewhere else in the code 644 struct Uniq(T) 645 if (is(T : Disposable) && !hasMemberFunc!(T, "release")) 646 { 647 private T _obj; 648 alias Resource = T; 649 650 // prevent using Uniq with Refcounted 651 // (invariant handles runtime polymorphism) 652 static assert(!is(T : RefCounted), "Use Rc helper for RefCounted objects"); 653 invariant() 654 { 655 // if obj is assigned, it must not cast to a RefCounted 656 assert(!_obj || !(cast(RefCounted)_obj), "Use Rc helper for RefCounted objects"); 657 } 658 659 /// Constructor taking rvalue. Uniqueness is achieve only if there is no 660 /// aliases of the passed reference. 661 this(T obj) 662 { 663 debug(Uniq) 664 { 665 writefln("build a Uniq!%s from rvalue", T.stringof); 666 } 667 _obj = obj; 668 } 669 670 /// Constructor taking lvalue. Uniqueness is achieve only if there is no 671 /// other copies of the passed reference. 672 this(ref T obj) 673 { 674 debug(Uniq) 675 { 676 writefln("build a Uniq!%s from lvalue", T.stringof); 677 } 678 _obj = obj; 679 obj = null; 680 } 681 682 /// Constructor that take a rvalue. 683 /// $(D u) can only be a rvalue because postblit is disabled. 684 this(U)(Uniq!U u) 685 if (is(U : T)) 686 { 687 debug(Uniq) 688 { 689 writefln("cast building a Uniq from rvalue from %s to %s", 690 U.stringof, T.stringof 691 ); 692 } 693 _obj = u._obj; 694 } 695 696 /// Transfer ownership from a Uniq of a type that is convertible to our type. 697 /// $(D u) can only be a rvalue because postblit is disabled. 698 void opAssign(U)(Uniq!U u) 699 if (is(U : T)) 700 { 701 debug(Uniq) 702 { 703 writefln("opAssign a Uniq from rvalue from %s to %s", 704 U.stringof, T.stringof 705 ); 706 } 707 if (_obj) 708 { 709 _obj.dispose(); 710 } 711 _obj = u._obj; 712 u._obj = null; 713 } 714 715 /// Shortcut to assigned 716 bool opCast(U : bool)() const 717 { 718 return assigned; 719 } 720 721 /// Destructor that disposes the resource. 722 ~this() 723 { 724 debug(Uniq) 725 { 726 writefln("dtor of Uniq!%s", T.stringof); 727 } 728 dispose(); 729 } 730 731 /// A view on the underlying object. 732 @property inout(T) obj() inout 733 { 734 return _obj; 735 } 736 737 /// Forwarding method calls and member access to the underlying object. 738 alias obj this; 739 740 /// Transfer the ownership. 741 Uniq release() 742 { 743 debug(Uniq) 744 { 745 writefln("release of Uniq!%s", T.stringof); 746 } 747 auto u = Uniq(_obj); 748 assert(_obj is null); 749 return u; 750 } 751 752 /// Explicitely ispose the underlying resource. 753 void dispose() 754 { 755 // Same method than Disposeable on purpose as it disables alias this. 756 // One cannot shortcut Uniq to dispose the resource. 757 if (_obj) 758 { 759 debug(Uniq) 760 { 761 writefln("dispose of Uniq!%s", T.stringof); 762 } 763 _obj.dispose(); 764 _obj = null; 765 } 766 } 767 768 /// Checks whether a resource is assigned. 769 bool assigned() const 770 { 771 return _obj !is null; 772 } 773 774 // disable copying 775 @disable this(this); 776 } 777 778 version(unittest) 779 { 780 private int disposeCount; 781 782 private class UniqTest : Disposable 783 { 784 override void dispose() 785 { 786 disposeCount += 1; 787 } 788 } 789 790 private Uniq!UniqTest produce1() 791 { 792 auto u = makeUniq!UniqTest(); 793 // Returning without release is fine? 794 // It compiles and passes the test, but not recommended. 795 // return u; 796 return u.release(); 797 } 798 799 private Uniq!UniqTest produce2() 800 { 801 return makeUniq!UniqTest(); 802 } 803 804 private void consume(Uniq!UniqTest /+u+/) 805 { 806 } 807 808 unittest 809 { 810 disposeCount = 0; 811 auto u = makeUniq!UniqTest(); 812 assert(disposeCount == 0); 813 static assert (!__traits(compiles, consume(u))); 814 consume(u.release()); 815 assert(disposeCount == 1); 816 817 { 818 auto v = makeUniq!UniqTest(); 819 } 820 assert(disposeCount == 2); 821 822 consume(produce1()); 823 assert(disposeCount == 3); 824 825 consume(produce2()); 826 assert(disposeCount == 4); 827 828 auto w = makeUniq!UniqTest(); 829 w.dispose(); 830 assert(disposeCount == 5); 831 } 832 } 833 834 private template hasMemberFunc(T, string fun) 835 { 836 enum bool hasMemberFunc = is(typeof( 837 (inout int = 0) 838 { 839 T t = T.init; 840 mixin("t."~fun~"();"); 841 })); 842 } 843 844 static assert(hasMemberFunc!(RefCounted, "retain")); 845 static assert(hasMemberFunc!(Disposable, "dispose")); 846 static assert(!hasMemberFunc!(Disposable, "release")); 847 }