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