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