1 /** 2 Numem Base Classes. 3 4 Copyright: 5 Copyright © 2000-2011, The D Language Foundation. 6 Copyright © 2023-2025, Kitsunebi Games 7 Copyright © 2023-2025, Inochi2D Project 8 9 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 10 Authors: Luna Nielsen, Walter Bright, Sean Kelly 11 12 See_Also: 13 $(D numem.core.memory.nu_resize) 14 $(D numem.core.memory.nu_dup) 15 $(D numem.core.memory.nu_idup) 16 */ 17 module numem.object; 18 import numem.lifetime; 19 import numem.core.exception; 20 import numem.core.lifetime : nu_autorelease; 21 import numem.core.traits; 22 import numem.core.hooks; 23 24 /** 25 Numem base-class which allows using basic class functions without a garbage 26 collector. 27 */ 28 class NuObject { 29 @nogc: 30 private: 31 32 TypeInfo_Class getClassInfo() { 33 return cast(TypeInfo_Class)typeid(this); 34 } 35 36 public: 37 38 /** 39 Gets the name of the class. 40 41 Returns: 42 Name of class based on its compile-time generated typeid. 43 */ 44 override 45 string toString() { 46 return getClassInfo().name; 47 } 48 49 /** 50 Test whether $(D this) is equal to $(D other). 51 52 Default implementation only checks whether they are stored at the same 53 memory address. 54 */ 55 override 56 bool opEquals(const Object other) nothrow const { 57 return this is other; 58 } 59 60 /** 61 Compare with another Object $(D other). 62 63 Returns: 64 $(TABLE 65 $(TR $(TD this < obj) $(TD < 0)) 66 $(TR $(TD this == obj) $(TD 0)) 67 $(TR $(TD this > obj) $(TD > 0)) 68 ) 69 70 Notes: 71 If you are combining this with a compacting garbage collector, 72 it will prevent it from functioning properly. 73 */ 74 override 75 int opCmp(const Object other) nothrow const { 76 return cast(int)cast(void*)this - cast(int)cast(void*)other; 77 } 78 79 /** 80 Compute a hash for Object. 81 */ 82 override 83 size_t toHash() @trusted nothrow { 84 85 // Address of ourselves. 86 size_t addr = cast(size_t)cast(void*)this; 87 88 // The bottom log2((void*).alignof) bits of the address will always 89 // be 0. Moreover it is likely that each Object is allocated with a 90 // separate call to malloc. The alignment of malloc differs from 91 // platform to platform, but rather than having special cases for 92 // each platform it is safe to use a shift of 4. To minimize 93 // collisions in the low bits it is more important for the shift to 94 // not be too small than for the shift to not be too big. 95 return addr ^ (addr >>> 4); 96 } 97 98 } 99 100 /** 101 A reference counted class. 102 103 Reference counted classes in numem are manually reference counted, 104 this means that you are responsible for managing synchronising 105 $(D retain) and $(D release) calls. 106 107 Threadsafety: 108 Threadsafety depends on whether the hookset used supports 109 atomic operations; see $(D numem.core.atomic.nu_atomic_supported). 110 If unsupported, retain and release will not be threadsafe on their own, 111 and should be wrapped in another synchronisation primitive. 112 113 Memorysafety: 114 Once the reference count for a class reaches 0, it will be destructed 115 and freed automatically. All references to the class after refcount 116 reaches 0 will be invalid and should not be used. 117 $(D NuRefCounted.release) returns a value which can be used to determine whether 118 the destructor was invoked. 119 */ 120 @nu_autoreleasewith!((ref obj) { obj.release(); }) 121 class NuRefCounted : NuObject { 122 @nogc: 123 private: 124 uint refcount = 0; 125 126 public: 127 128 /** 129 The current reference count of the object. 130 */ 131 final @property uint refs() => nu_atomic_load_32(refcount); 132 133 /** 134 Base constructor, all subclasses *have* to invoke this constructor. 135 Otherwise the instance will be invalid on instantiation. 136 */ 137 this() @safe { 138 refcount = 1; 139 } 140 141 /** 142 Retains a reference to a valid object. 143 144 Notes: 145 The object validity is determined by the refcount. 146 Uninitialized refcounted classes will be invalid. 147 As such, releasing an invalid object will not 148 invoke the destructor of said object. 149 */ 150 final 151 NuRefCounted retain() @trusted nothrow { 152 if (isValid) 153 nu_atomic_add_32(refcount, 1); 154 155 return this; 156 } 157 158 /** 159 Releases a reference from a valid object. 160 161 Notes: 162 The object validity is determined by the refcount. 163 Uninitialized refcounted classes will be invalid. 164 As such, releasing an invalid object will not 165 invoke the destructor of said object. 166 167 Returns: 168 The class instance release was called on, $(D null) if 169 the class was freed. 170 */ 171 final 172 NuRefCounted release() @trusted { 173 if (isValid) { 174 nu_atomic_sub_32(refcount, 1); 175 176 // Handle destruction. 177 if (nu_atomic_load_32(refcount) == 0) { 178 NuRefCounted self = this; 179 nogc_delete(self); 180 return null; 181 } 182 } 183 184 return this; 185 } 186 187 /** 188 Pushes this refcounted object to the topmost auto release pool. 189 190 Returns: 191 The class instance release was called on. 192 */ 193 final 194 NuRefCounted autorelease() @trusted { 195 nu_autorelease!NuRefCounted(this); 196 return this; 197 } 198 199 /** 200 Returns whether this object is valid. 201 202 Notes: 203 The object validity is determined by the refcount. 204 Uninitialized refcounted classes will be invalid. 205 As such, releasing an invalid object will not 206 invoke the destructor of said object. 207 208 Returns: 209 Whether the object is valid (has a refcount higher than 0) 210 */ 211 final 212 bool isValid() @trusted nothrow { 213 return nu_atomic_load_32(refcount) != 0; 214 } 215 } 216 217 /** 218 Helper function which allows typed chaining of retain calls. 219 220 Params: 221 elem = The element to perform the operation on 222 223 Returns: 224 The element or $(D null) if it was freed as a result 225 of the operation. 226 */ 227 T retained(T)(T elem) @nogc @trusted if (isRefcounted!T) { 228 import numem.core.memory : nu_retain; 229 return cast(T)elem.nu_retain(); 230 } 231 232 /// ditto 233 T released(T)(T elem) @nogc @trusted if (isRefcounted!T) { 234 import numem.core.memory : nu_release; 235 return cast(T)elem.nu_release(); 236 } 237 238 /// ditto 239 T autoreleased(T)(T elem) @nogc @trusted if (is(T : NuRefCounted)) { 240 return cast(T)elem.autorelease(); 241 }