1 /* 2 MIT License 3 Copyright (c) 2017 Boris Barboris 4 */ 5 6 module daii.refcounted; 7 8 import core.atomic: atomicOp; 9 import std.experimental.allocator: make, dispose; 10 import std.experimental.allocator.mallocator: Mallocator; 11 import std.functional: forward; 12 import std.traits: isArray, isAbstractClass, isAssignable; 13 14 import daii.utils; 15 16 17 template AllocationContext(Allocator = Mallocator, bool Atomic = true) 18 if (isAllocator!Allocator) 19 { 20 /// Reference-counting memory owner, holds one shared instance of type T. 21 /// Don't use it to hold built-in arrays, use custom array type instead. 22 /// Deallocates in destructor, when reference count is zero. 23 struct RefCounted(T) 24 if (!isArray!T) 25 { 26 enum HoldsAllocator = !isStaticAllocator!Allocator; 27 28 static if (isClassOrIface!T) 29 { 30 alias PtrT = T; 31 @property inout(T) v() inout @nogc @safe 32 { 33 assert(valid); 34 return ptr; 35 } 36 } 37 else 38 { 39 alias PtrT = T*; 40 @property ref inout(T) v() inout @nogc @safe 41 { 42 assert(valid); 43 return *ptr; 44 } 45 } 46 47 static if (Atomic) 48 alias RefCounterT = shared size_t; 49 else 50 alias RefCounterT = size_t; 51 52 private RefCounterT* refcount; 53 private PtrT ptr; 54 55 @property bool valid() const @nogc @safe 56 { 57 return (refcount !is null) && (*refcount > 0); 58 } 59 60 @disable this(); 61 62 static if (HoldsAllocator) 63 private Allocator allocator; 64 65 // don't generate constructors for abstract types 66 static if (!(is(T == interface) || isAbstractClass!(T))) 67 { 68 static if (!HoldsAllocator) 69 { 70 package this(PtrT ptr) 71 { 72 this.refcount = cast(RefCounterT*) Allocator.instance.make!size_t(1); 73 this.ptr = ptr; 74 } 75 76 static RefCounted!T 77 make(Args...)(auto ref Args args) 78 { 79 auto ptr = Allocator.instance.make!(T)(forward!args); 80 auto rq = RefCounted!(T)(ptr); 81 assert(rq.valid); 82 return rq; 83 } 84 } 85 else 86 { 87 package this(PtrT ptr, Allocator alloc) 88 { 89 this.refcount = cast(RefCounterT*) alloc.make!size_t(1); 90 this.ptr = ptr; 91 this.allocator = alloc; 92 } 93 94 static RefCounted!(T) 95 make(Args...)(auto ref Allocator alloc, auto ref Args args) 96 { 97 auto ptr = alloc.make!(T)(forward!args); 98 auto rq = RefCounted!(T)(ptr, alloc); 99 assert(rq.valid); 100 return rq; 101 } 102 } 103 } 104 105 // bread and butter 106 ~this() 107 { 108 decrement(); 109 } 110 111 // destroy the resource (destructor + free memory) 112 private void destroy() 113 { 114 static if (HoldsAllocator) 115 { 116 allocator.dispose(ptr); 117 allocator.dispose(refcount); 118 } 119 else 120 { 121 Allocator.instance.dispose(ptr); 122 Allocator.instance.dispose(refcount); 123 } 124 ptr = null; 125 refcount = null; 126 } 127 128 private void decrement() 129 { 130 assert(valid); 131 static if (Atomic) 132 { 133 if (atomicOp!"-="(*refcount, 1) == 0) 134 destroy(); 135 } 136 else 137 { 138 if ((*refcount -= 1) == 0) 139 destroy(); 140 } 141 } 142 143 private void increment() @safe @nogc 144 { 145 assert(valid); 146 static if (Atomic) 147 atomicOp!"+="(*refcount, 1); 148 else 149 *refcount += 1; 150 } 151 152 this(this) @safe @nogc 153 { 154 increment(); 155 } 156 157 // handle polymorphism 158 static if (isClassOrIface!T) 159 { 160 // Polymorphism-aware assign operator 161 ref RefCounted!(T) 162 opAssign(DT)(const RefCounted!(DT) rhs) 163 if (isClassOrIface!DT && isAssignable!(T, DT)) 164 { 165 decrement(); 166 this.ptr = cast(PtrT) rhs.ptr; 167 this.refcount = cast(RefCounterT*) rhs.refcount; 168 static if (HoldsAllocator) 169 this.allocator = cast(Allocator) rhs.allocator; 170 if (valid) 171 increment(); 172 return this; 173 } 174 175 // Polymorphism-aware constructor 176 this(DT)(const RefCounted!(DT) rhs) @trusted 177 if (isClassOrIface!DT && isAssignable!(T, DT)) 178 { 179 this.ptr = cast(PtrT) rhs.ptr; 180 this.refcount = cast(RefCounterT*) rhs.refcount; 181 static if (HoldsAllocator) 182 this.allocator = cast(Allocator) rhs.allocator; 183 if (valid) 184 increment(); 185 } 186 187 // Polymorphic upcast 188 RefCounted!(BT) to(BT)() const @trusted 189 if (isClassOrIface!BT && isAssignable!(BT, T)) 190 { 191 assert(valid); 192 RefCounted!(BT) rv = RefCounted!(BT)(this); 193 assert(rv.valid); 194 return rv; 195 } 196 } 197 else 198 { 199 ref opAssign(const RefCounted!(T) rhs) 200 { 201 decrement(); 202 this.ptr = cast(PtrT) rhs.ptr; 203 this.refcount = cast(RefCounterT*) rhs.refcount; 204 static if (HoldsAllocator) 205 this.allocator = cast(Allocator) rhs.allocator; 206 if (valid) 207 increment(); 208 return this; 209 } 210 } 211 } 212 } 213 214 private template RefCounted(T) 215 { 216 alias RefCounted = AllocationContext!(Mallocator, true).RefCounted!T; 217 } 218 219 unittest 220 { 221 auto uq = RefCounted!int.make(5); 222 assert(uq.valid); 223 assert(uq.v == 5); 224 uq.v = 7; 225 assert(uq.v == 7); 226 } 227 228 unittest 229 { 230 auto create() 231 { 232 return RefCounted!int.make(0); 233 } 234 void consume(RefCounted!int u) 235 { 236 assert(u.valid); 237 assert(*u.refcount == 2); 238 assert(u.v == 5); 239 } 240 auto u = create(); 241 assert(u.valid); 242 assert(*u.refcount == 1); 243 assert(u.v == 0); 244 u.v = 5; 245 consume(u); 246 assert(*u.refcount == 1); 247 } 248 249 unittest 250 { 251 struct TS 252 { 253 int x = -3; 254 } 255 auto u = RefCounted!TS.make; 256 assert(u.v.x == -3); 257 } 258 259 unittest 260 { 261 static int count = 0; 262 static int total = 0; 263 class TC 264 { 265 this() { count++; total++; } 266 ~this() { count--; } 267 int j = 3; 268 } 269 auto u1 = RefCounted!TC.make; 270 assert(count == 1); 271 { 272 auto u2 = RefCounted!TC.make; 273 assert(count == 2); 274 assert(u2.v !is null); 275 assert(u2.v.j == 3); 276 u2 = u1; 277 assert(count == 1); 278 assert(u1.valid); 279 assert(u2.valid); 280 assert(*(u1.refcount) == 2); 281 } 282 assert(count == 1); 283 assert(*(u1.refcount) == 1); 284 assert(total == 2); 285 } 286 287 import std.experimental.allocator.showcase; 288 289 unittest 290 { 291 static int count = 0; 292 static int total = 0; 293 class TC 294 { 295 this() { count++; total++; } 296 ~this() { count--; } 297 int j = 3; 298 } 299 alias Alloc = StackFront!(4096, Mallocator); 300 Alloc al; 301 alias Uq = AllocationContext!(Alloc*, true).RefCounted!(TC); 302 auto u1 = Uq.make(&al); 303 assert(count == 1); 304 { 305 auto u2 = Uq.make(&al); 306 assert(count == 2); 307 assert(u2.v !is null); 308 assert(u2.v.j == 3); 309 u2 = u1; 310 assert(count == 1); 311 assert(u1.valid); 312 assert(u2.valid); 313 assert(*(u1.refcount) == 2); 314 } 315 assert(count == 1); 316 assert(*(u1.refcount) == 1); 317 assert(total == 2); 318 } 319 320 unittest 321 { 322 static int ades = 0; 323 static int bdes = 0; 324 class A { ~this() {ades++;}} 325 class B: A { ~this(){bdes++;}} 326 { 327 RefCounted!A ag = RefCounted!A.make(); 328 { 329 RefCounted!A aq = ag; 330 assert(aq.valid && ag.valid); 331 { 332 RefCounted!A bq = RefCounted!B.make(); 333 assert(ades == 0); 334 assert(bdes == 0); 335 } 336 assert(ades == 1); 337 assert(bdes == 1); 338 } 339 assert(ades == 1); 340 assert(bdes == 1); 341 } 342 assert(ades == 2); 343 assert(bdes == 1); 344 } 345 346 unittest 347 { 348 static int ades = 0; 349 static int bdes = 0; 350 class A { ~this() {ades++;}} 351 class B: A { ~this(){bdes++;}} 352 void consume(RefCounted!A uq) 353 { 354 assert(uq.valid); 355 } 356 { 357 RefCounted!B aq = RefCounted!B.make(); 358 consume(aq.to!A); 359 assert(ades == 0); 360 assert(bdes == 0); 361 } 362 assert(ades == 1); 363 assert(bdes == 1); 364 }