1 /** 2 A reference-counted smart pointer. 3 */ 4 module automem.ref_counted; 5 6 import automem.traits: isAllocator; 7 import automem.unique: Unique; 8 import stdx.allocator: theAllocator, processAllocator; 9 import std.typecons: Flag; 10 11 12 alias RC = RefCounted; 13 14 /** 15 A reference-counted smart pointer similar to C++'s std::shared_ptr. 16 */ 17 struct RefCounted(Type, 18 Allocator = typeof(theAllocator), 19 Flag!"supportGC" supportGC = Flag!"supportGC".yes) 20 if(isAllocator!Allocator) 21 { 22 23 import std.traits: hasMember; 24 25 enum isSingleton = hasMember!(Allocator, "instance"); 26 enum isTheAllocator = is(Allocator == typeof(theAllocator)); 27 enum isGlobal = isSingleton || isTheAllocator; 28 29 static if(isGlobal) 30 /** 31 The allocator is a singleton, so no need to pass it in to the 32 constructor 33 */ 34 this(Args...)(auto ref Args args) { 35 this.makeObject!args(); 36 } 37 else 38 /** 39 Non-singleton allocator, must be passed in 40 */ 41 this(Args...)(Allocator allocator, auto ref Args args) { 42 _allocator = allocator; 43 this.makeObject!args(); 44 } 45 46 static if(isGlobal) 47 /** 48 Factory method so can construct with zero args. 49 */ 50 static typeof(this) construct(Args...)(auto ref Args args) { 51 static if (Args.length != 0) 52 return typeof(return)(args); 53 else { 54 typeof(return) ret; 55 ret.makeObject!()(); 56 return ret; 57 } 58 } 59 else 60 /** 61 Factory method. Not necessary with non-global allocator 62 but included for symmetry. 63 */ 64 static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) { 65 return typeof(return)(allocator, args); 66 } 67 68 /// 69 this(this) { 70 // assert(_impl !is null, "Postblit ctor with null impl"); 71 // inc; 72 if(_impl !is null) inc; 73 } 74 75 /// 76 ~this() { 77 release; 78 } 79 80 /** 81 Assign to an lvalue RefCounted 82 */ 83 void opAssign(ref RefCounted other) { 84 85 if (_impl == other._impl) return; 86 87 if(_impl !is null) release; 88 89 static if(!isGlobal) 90 _allocator = other._allocator; 91 92 _impl = other._impl; 93 94 if(_impl !is null) inc; 95 } 96 97 /** 98 Assign to an rvalue RefCounted 99 */ 100 void opAssign(RefCounted other) { 101 import std.algorithm: swap; 102 swap(_impl, other._impl); 103 static if(!isGlobal) 104 swap(_allocator, other._allocator); 105 } 106 107 /** 108 Dereference the smart pointer and yield a reference 109 to the contained type. 110 */ 111 ref auto opUnary(string s)() inout if (s == "*") { 112 return _impl._get; 113 } 114 115 /** 116 Prevent opSlice and opIndex from being hidden by Impl*. 117 This comment is deliberately not DDOC. 118 */ 119 auto ref opSlice(A...)(auto ref A args) 120 if (__traits(compiles, Type.init.opSlice(args))) 121 { 122 return _impl._get.opSlice(args); 123 } 124 /// ditto 125 auto ref opIndex(A...)(auto ref A args) 126 if (__traits(compiles, Type.init.opIndex(args))) 127 { 128 return _impl._get.opIndex(args); 129 } 130 /// ditto 131 auto ref opIndexAssign(A...)(auto ref A args) 132 if (__traits(compiles, Type.init.opIndexAssign(args))) 133 { 134 return _impl._get.opIndexAssign(args); 135 } 136 137 alias _impl this; 138 139 private: 140 141 static struct Impl { 142 143 static if(is(Type == class)) { 144 145 align ((void*).alignof) 146 void[__traits(classInstanceSize, Type)] _rawMemory; 147 148 } else 149 Type _object; 150 151 static if(is(Type == shared)) 152 shared size_t _count; 153 else 154 size_t _count; 155 156 static if (is(Type == class)) { 157 inout(Type) _get() inout { 158 return cast(inout(Type))&_rawMemory[0]; 159 } 160 161 inout(shared(Type)) _get() inout shared { 162 return cast(inout(shared(Type)))&_rawMemory[0]; 163 } 164 } else { 165 ref inout(Type) _get() inout { 166 return _object; 167 } 168 169 ref inout(shared(Type)) _get() inout shared { 170 return _object; 171 } 172 } 173 174 alias _get this; 175 } 176 177 static if(isSingleton) 178 alias _allocator = Allocator.instance; 179 else static if(isTheAllocator) { 180 static if (is(Type == shared)) 181 // 'processAllocator' should be used for allocating 182 // memory shared across threads 183 alias _allocator = processAllocator; 184 else 185 alias _allocator = theAllocator; 186 } 187 else 188 Allocator _allocator; 189 190 static if(is(Type == shared)) 191 alias ImplType = shared Impl; 192 else 193 alias ImplType = Impl; 194 195 public ImplType* _impl; // public or alias this doesn't work 196 197 void allocateImpl() { 198 import stdx.allocator: make; 199 import std.traits: hasIndirections; 200 201 _impl = cast(typeof(_impl))_allocator.allocate(Impl.sizeof); 202 _impl._count= 1; 203 204 static if (is(Type == class)) { 205 // class representation: 206 // void* classInfoPtr 207 // void* monitorPtr 208 // [] interfaces 209 // T... members 210 import core.memory: GC; 211 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) 212 // members have pointers: we have to watch the monitor 213 // and all members; skip the classInfoPtr 214 GC.addRange(&_impl._rawMemory[(void*).sizeof], 215 __traits(classInstanceSize, Type) - (void*).sizeof); 216 else 217 // representation doesn't have pointers, just watch the 218 // monitor pointer; skip the classInfoPtr 219 // need to watch the monitor pointer even if supportGC is false. 220 GC.addRange(&_impl._rawMemory[(void*).sizeof], (void*).sizeof); 221 } else static if (supportGC && hasIndirections!Type) { 222 import core.memory: GC; 223 GC.addRange(cast(void*) &_impl._object, Type.sizeof); 224 } 225 } 226 227 void release() { 228 import std.traits : hasIndirections; 229 import core.memory : GC; 230 import automem.utils : destruct; 231 232 if(_impl is null) return; 233 assert(_impl._count > 0, "Trying to release a RefCounted but ref count is 0 or less"); 234 235 dec; 236 237 if(_impl._count == 0) { 238 () @trusted { destruct(_impl._get); }(); 239 static if (is(Type == class)) { 240 // need to watch the monitor pointer even if supportGC is false. 241 () @trusted { GC.removeRange(&_impl._rawMemory[(void*).sizeof]); }(); 242 } else static if (supportGC && hasIndirections!Type) { 243 () @trusted { GC.removeRange(cast(void*) &_impl._object); }(); 244 } 245 auto mem = () @trusted { return cast(void*)_impl; }(); 246 () @trusted { _allocator.deallocate(mem[0 .. Impl.sizeof]); }(); 247 } 248 } 249 250 void inc() { 251 static if(is(Type == shared)) { 252 import core.atomic: atomicOp; 253 _impl._count.atomicOp!"+="(1); 254 } else 255 ++_impl._count; 256 257 } 258 259 void dec() { 260 static if(is(Type == shared)) { 261 import core.atomic: atomicOp; 262 _impl._count.atomicOp!"-="(1); 263 } else 264 --_impl._count; 265 } 266 267 } 268 269 private template makeObject(args...) 270 { 271 void makeObject(Type, A)(ref RefCounted!(Type, A) rc) @trusted { 272 import std.conv: emplace; 273 import std.functional : forward; 274 275 rc.allocateImpl; 276 277 static if(is(Type == class)) 278 emplace!Type(rc._impl._rawMemory, forward!args); 279 else 280 emplace(&rc._impl._object, forward!args); 281 } 282 } 283 284 285 286 auto refCounted(Type, Allocator)(Unique!(Type, Allocator) ptr) { 287 288 RefCounted!(Type, Allocator) ret; 289 290 static if(!ptr.isGlobal) 291 ret._allocator = ptr.allocator; 292 293 ret.allocateImpl; 294 *ret = *ptr; 295 296 return ret; 297 }