You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

558 lines
16 KiB

1 year ago
  1. #pragma once
  2. #include <fsdk/IObject.h>
  3. #include <cstdint>
  4. #include <cstdlib>
  5. #include <atomic>
  6. #include <new>
  7. namespace tsdk {
  8. /**
  9. * @brief Default base reference-counted object implementation.
  10. * RefCountedImpl derivatives must apply ot the following rules:
  11. * - be descendants of IRefCounted
  12. * - objects must be created strictly on heap with no exceptions
  13. * - new and delete should not be accessible directly (unless you understand good enough what you are doing).
  14. * RefCountedImpl provides implementation of weak reference counting to coupe with functions defined by IRefCounted.
  15. * Weak referencing allows to avoid circular dependencies in parent-child relationships. Weak reference count is
  16. * controlled via retainWeak(), releaseWeak() and getWeakRefCount(). These functions behave similar to their 'strong'
  17. * reference counting relatives, but control life time just of the reference counter objects instead of the counted
  18. * object (which occupies less memory). This way we are able to call destructor of the counting object and
  19. * potentially free it's resources and keep the counter alive at the same time to avoid poking a dead object.
  20. *
  21. *
  22. * @tparam T base interface type to propagate through inheritance tree. Must be convertible to IRefCounted.
  23. * @note RefCountedImpl uses a separate reference counter object which is prepended to the actual object during
  24. * construction so size of occupied memory is greater than generally expected sizeof(ObjectType).
  25. * @note this implementation of RefCountedImpl is thread safe.
  26. * @note to achieve lock free thread safety weak reference counter reflects actual value of strong reference
  27. * counter. I.e. after creation, the object has both counters set to 1. After each retain() both counters increase
  28. * and so on. retainWeak() / releaseWeak() in turn alter only the weak reference counter. So actual number of
  29. * weak references is Nact = (Nweak - Nstrong).
  30. * */
  31. template<typename T>
  32. struct RefCountedImpl : T {
  33. /**
  34. * @brief Reference counter object.
  35. * Keeps track of strong and weak references.
  36. * */
  37. struct RefCounter {
  38. std::atomic<int32_t> m_strong; //!< strong reference count.
  39. std::atomic<int32_t> m_weak; //!< weak reference count.
  40. };
  41. /**
  42. * @brief Acquire strong reference.
  43. * @note this function is thread safe.
  44. * @returns actual reference count.
  45. * */
  46. int32_t retain() noexcept override {
  47. auto counter = getCounter(this);
  48. // object must be alive.
  49. assert(counter->m_strong > 0);
  50. assert(counter->m_weak > 0);
  51. return (++ counter->m_strong);
  52. }
  53. /**
  54. * @brief Acquire strong reference thread safely.
  55. * @note difference between retain and retainLocked is former guarantees synchronization in case another
  56. * thread release object in the same time we try to call WeakRef.lock()
  57. * This method was introduced to align behaviour of WeakRef.lock() from SDK with with weak_ptr.lock()
  58. * from standard library, so seek more info about thread safety requirements for std::weak_ptr.
  59. * @returns actual reference count.
  60. * */
  61. int32_t retainLocked() noexcept override {
  62. auto counter = getCounter(this);
  63. int32_t strongCount = counter->m_strong;
  64. do {
  65. // if counter hit 0 at any point during object lifetime, it means it can't be used anymore, so don't retain
  66. if(strongCount == 0)
  67. return 0;
  68. } while(!std::atomic_compare_exchange_weak_explicit(
  69. &counter->m_strong,
  70. &strongCount,
  71. strongCount + 1,
  72. std::memory_order_acq_rel,
  73. std::memory_order_relaxed));
  74. // strong count + 1 reflects actual value in any of cases, and it's faster to read non-atomic variable
  75. return strongCount + 1;
  76. }
  77. /**
  78. * @brief Release strong reference.
  79. * @note this function is thread safe.
  80. * @returns actual reference count.
  81. * */
  82. int32_t release() noexcept override {
  83. auto counter = getCounter(this);
  84. // object must be alive.
  85. assert(counter->m_strong > 0);
  86. assert(counter->m_weak > 0);
  87. const int32_t strong = (-- counter->m_strong);
  88. if(strong == 0) {
  89. destruct();
  90. // for thread fence reasons see http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
  91. std::atomic_thread_fence(std::memory_order_acq_rel);
  92. if((-- counter->m_weak) == 0) {
  93. deallocate();
  94. }
  95. }
  96. return strong;
  97. }
  98. /**
  99. * @brief Get actual strong reference count.
  100. * @note this function is thread safe.
  101. * @returns actual reference count.
  102. * */
  103. int32_t getRefCount() const noexcept override {
  104. auto counter = getCounter(this);
  105. return counter->m_strong;
  106. }
  107. /**
  108. * @brief Acquire weak reference.
  109. * @note this function is thread safe.
  110. * @returns actual reference count.
  111. * */
  112. int32_t retainWeak() noexcept override {
  113. auto counter = getCounter(this);
  114. // object reference counter must be alive.
  115. assert(counter->m_weak > 0);
  116. return (++ counter->m_weak);
  117. }
  118. /**
  119. * @brief Release weak reference.
  120. * @note this function is thread safe.
  121. * @returns actual reference count.
  122. * */
  123. int32_t releaseWeak() noexcept override {
  124. auto counter = getCounter(this);
  125. // object reference counter must be alive.
  126. assert(counter->m_weak > 0);
  127. const int32_t weak = (-- counter->m_weak);
  128. if(weak == 0) {
  129. // for thread fence reasons see http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
  130. std::atomic_thread_fence(std::memory_order_acq_rel);
  131. deallocate();
  132. }
  133. return weak;
  134. }
  135. /**
  136. * @brief Get weak reference count.
  137. * @note this function is thread safe.
  138. * @returns actual reference count.
  139. * */
  140. int32_t getWeakRefCount() const noexcept override {
  141. auto counter = getCounter(this);
  142. return counter->m_weak;
  143. }
  144. /**
  145. * @brief Check if object is dead.
  146. * @note this function is thread safe.
  147. * @returns true if strong reference count is zero, false otherwise.
  148. * */
  149. bool isExpired() { return getRefCount() == 0; }
  150. /**
  151. * @brief Check if object is alive.
  152. * @note this function is thread safe.
  153. * @returns true if strong reference count is not zero, false otherwise.
  154. * */
  155. bool isAlive() { return !isExpired(); }
  156. protected:
  157. /**
  158. * @brief Initialize empty object.
  159. * @details Stores a pointer to reference counter in debug builds.
  160. * */
  161. RefCountedImpl() {
  162. #ifndef NDEBUG
  163. m_counter = getCounter(this);
  164. #endif
  165. RefCounter *counter = getCounter(this);
  166. counter->m_strong++;
  167. counter->m_weak++;
  168. }
  169. virtual ~RefCountedImpl() {
  170. if(getRefCount() && getWeakRefCount()) {
  171. RefCounter *counter = getCounter(this);
  172. counter->m_strong--;
  173. counter->m_weak--;
  174. }
  175. }
  176. /**
  177. * @brief Custom reference-counter aware operator new.
  178. * Uses default CRT implementation of malloc().
  179. * @param size memory size to allocate.
  180. * */
  181. void* operator new(size_t size) {
  182. auto counter =
  183. reinterpret_cast<RefCounter*>(
  184. malloc(size + sizeof(RefCounter)));
  185. new (counter) RefCounter();
  186. counter->m_strong = 0;
  187. counter->m_weak = 0;
  188. return counter + 1;
  189. }
  190. /**
  191. * @brief Custom reference-counter aware operator delete.
  192. * Uses default CRT implementation of free.
  193. * @param memory memory location to free.
  194. * @param memory memory size to free. (Unused).
  195. * */
  196. void operator delete(void* memory, size_t) {
  197. auto counter =
  198. reinterpret_cast<RefCounter*>(memory) - 1;
  199. assert(counter->m_strong == 0);
  200. assert(counter->m_weak == 0);
  201. counter->~RefCounter();
  202. free(counter);
  203. }
  204. /**
  205. * @brief Call operator delete on this object.
  206. * @note no safety checks performed.
  207. * */
  208. void deallocate() {
  209. operator delete (this, 0);
  210. }
  211. /**
  212. * @brief Call operator destructor on this object.
  213. * @note no safety checks performed.
  214. * */
  215. void destruct() {
  216. this->~RefCountedImpl();
  217. }
  218. /**
  219. * @brief Get object reference counter pointer from this pointer.
  220. * @param pthis this pointer.
  221. * */
  222. static RefCounter* getCounter(RefCountedImpl* pthis) {
  223. return reinterpret_cast<RefCounter*>(pthis) - 1;
  224. }
  225. /**
  226. * @brief Get object reference counter pointer from this pointer.
  227. * @param pthis this pointer.
  228. * */
  229. static const RefCounter* getCounter(const RefCountedImpl* pthis) {
  230. return reinterpret_cast<const RefCounter*>(pthis) - 1;
  231. }
  232. #ifndef NDEBUG
  233. RefCounter* m_counter; //!< pointer to reference counter object.
  234. #endif
  235. };
  236. /** @brief Plain reference counted base. */
  237. typedef RefCountedImpl<fsdk::IRefCounted> RefCounted;
  238. /**
  239. * @brief Default base object implementation.
  240. * @tparam T object interface, must be convertible to IObject.
  241. * @tparam ID object type id.
  242. * */
  243. template<typename T, int32_t ID>
  244. struct ObjectImpl : RefCountedImpl<T> {
  245. /**
  246. * @brief Get object type id.
  247. * @note this function is thread safe.
  248. * @returns object type id.
  249. * */
  250. int32_t getTypeId() const override {
  251. return ID;
  252. }
  253. };
  254. /**
  255. * @brief Default public object implementation.
  256. * @note this expects embedded object type id in to the interface. This is achieved with DECLARE_TYPE_ID macro.
  257. * @tparam T object interface, must be convertible to IObject.
  258. * */
  259. template<typename T>
  260. struct PublicObjectImpl : RefCountedImpl<T> {
  261. };
  262. /**
  263. * @brief Default base reference-counted managed object implementation.
  264. * This implementation assumes that implemented object has a logical parent object that holds a pool for it's
  265. * children or implements any other method of life time management. Thus, parent type must apply to several more
  266. * restrictions than the managed type.
  267. * @tparam T base interface type to propagate through inheritance tree. Must be convertible to IRefCounted.
  268. * @tparam P parent object type. Must be convertible to RefCountedImpl.
  269. * P must implement the following interface:
  270. * [code]
  271. * void recycle(C*);
  272. * [/code]
  273. * where C is any type that is convertible to ManagedRefCountedImpl<T, P>.
  274. * @note consequently it is safe to deallocate dead children in destructor since ones that are alive (if any) are
  275. * already aware that their parent object is dead before the actual call of destructor, so will not try to return
  276. * themselves to the parent's pool.
  277. * */
  278. template<
  279. typename T,
  280. typename P>
  281. struct ManagedRefCountedImpl : RefCountedImpl<T> {
  282. /** @brief Managed object type. */
  283. typedef ManagedRefCountedImpl<T, P> ManagedType;
  284. /** @brief Parent object type. */
  285. typedef P ParentType;
  286. /**
  287. * @brief Get a parent object.
  288. * Any returned interfaces will have their reference count incremented by one, so be sure to call ::release() on
  289. * the returned pointer(s) before they are freed or else you will have a memory leak.
  290. * @returns pointer to the parent object or nullptr if parent is expired.
  291. * */
  292. ParentType* getParent() const {
  293. // return strong reference to pointer if it is alive.
  294. if(auto parent = m_parent.lock()) {
  295. // ensure it will be alive until caller deals with it.
  296. // if retain returns 0 it means it has failed
  297. if(parent->retain() != 0)
  298. return parent;
  299. }
  300. // parent is dead, so return nullptr.
  301. return nullptr;
  302. }
  303. /**
  304. * @brief Release strong reference.
  305. * Recycles object.
  306. * @note this function is thread safe.
  307. * @returns actual reference count.
  308. * */
  309. int32_t release() noexcept override {
  310. auto counter = this->getCounter(this);
  311. // object must be alive.
  312. assert(counter->m_strong > 0);
  313. assert(counter->m_weak > 0);
  314. const int32_t strong = (-- counter->m_strong);
  315. if(strong == 0 && (-- counter->m_weak) == 0) {
  316. // don't call dtor as we expect this object to be renewed later.
  317. this->recycle();
  318. }
  319. return strong;
  320. }
  321. /**
  322. * @brief Release weak reference.
  323. * Recycles object.
  324. * @note this function is thread safe.
  325. * @returns actual reference count.
  326. * */
  327. int32_t releaseWeak() noexcept override {
  328. auto counter = this->getCounter(this);
  329. // object reference counter must be alive.
  330. assert(counter->m_weak > 0);
  331. const int32_t weak = (-- counter->m_weak);
  332. if(weak == 0) {
  333. this->recycle();
  334. }
  335. return weak;
  336. }
  337. protected:
  338. // Befriend our parent so that it can call destruct() and deallocate().
  339. friend P;
  340. /**
  341. * @brief Initialize an empty object with a parent.
  342. * @param parent the parent.
  343. * */
  344. explicit ManagedRefCountedImpl(ParentType* parent)
  345. : m_parent(parent)
  346. { assert(parent); }
  347. /**
  348. * @brief Try recycle this object.
  349. * If parent is still alive, then return to it's cache.
  350. * If parent is dead, free memory.
  351. * */
  352. void recycle() noexcept {
  353. // return to reuse pool or free memory.
  354. if(auto parent = m_parent.lock()) {
  355. // remove dangling weak ref.
  356. m_parent.reset();
  357. // recycle may call deallocate() so no code should follow this point.
  358. parent->recycle(this);
  359. }
  360. else {
  361. // our parent is already dead, so it goes.
  362. this->destroy();
  363. }
  364. }
  365. /**
  366. * @brief Reanimate this object by resetting reference counters.
  367. * @details Due to implementation details parent object pointer should re-assigned.
  368. * @param parent parent object pointer.
  369. * */
  370. void reanimate(ParentType* parent) {
  371. assert(parent);
  372. auto counter = this->getCounter(this);
  373. // Make sure object is dead;
  374. // otherwise it's a logical issue.
  375. assert(counter->m_strong == 0);
  376. assert(counter->m_weak == 0);
  377. counter->m_weak = 1;
  378. counter->m_strong = 1;
  379. m_parent.assign(parent);
  380. }
  381. /**
  382. * @brief Calls both destructor and operator delete.
  383. */
  384. void destroy() noexcept {
  385. this->destruct();
  386. this->deallocate();
  387. }
  388. private:
  389. fsdk::WeakRef<ParentType> m_parent; //!< parent (pool) object.
  390. };
  391. /**
  392. * @brief Default base managed object implementation.
  393. * @tparam T object interface, must be convertible to IObject.
  394. * @tparam ID object type id.
  395. * @tparam P parent object type, @see ManagedRefCountedImpl.
  396. * */
  397. template<
  398. typename T, int32_t ID,
  399. typename P>
  400. struct ManagedObjectImpl : ManagedRefCountedImpl<T, P> {
  401. /** @brief Managed object type. */
  402. typedef ManagedObjectImpl<T, ID, P> ManagedType;
  403. // Fix dependent name lookup.
  404. using typename ManagedRefCountedImpl<T, P>::ParentType;
  405. /**
  406. * @brief Get object type id.
  407. * @note this function is thread safe.
  408. * @returns object type id.
  409. * */
  410. int32_t getTypeId() const override {
  411. return ID;
  412. }
  413. /**
  414. * @brief Get a parent object.
  415. * Any returned interfaces will have their reference count incremented by one, so be sure to call ::release() on
  416. * the returned pointer(s) before they are freed or else you will have a memory leak.
  417. * @returns pointer to the parent object or nullptr if parent is expired.
  418. * */
  419. fsdk::IRefCounted* getParentObject() const noexcept {
  420. return this->getParent();
  421. }
  422. protected:
  423. /**
  424. * @brief Initialize an empty object with a parent.
  425. * @param parent the parent.
  426. * */
  427. explicit ManagedObjectImpl(ParentType* parent)
  428. : ManagedRefCountedImpl<T, P>(parent) {}
  429. };
  430. /**
  431. * @brief Default public managed object implementation.
  432. * @note this expects embedded object type id in to the interface. This is achieved with DECLARE_TYPE_ID macro.
  433. * @tparam T object interface, must be convertible to IObject.
  434. * @tparam P parent object type, @see ManagedRefCountedImpl.
  435. * */
  436. template<
  437. typename T,
  438. typename P>
  439. struct ManagedPublicObjectImpl : ManagedRefCountedImpl<T, P> {
  440. /** @brief Managed object type. */
  441. typedef ManagedPublicObjectImpl<T, P> ManagedType;
  442. ///Fix dependent name lookup.
  443. using typename ManagedRefCountedImpl<T, P>::ParentType;
  444. /** @brief Get a parent object.
  445. * Any returned interfaces will have their reference count incremented by one, so be sure to call ::release() on
  446. * the returned pointer(s) before they are freed or else you will have a memory leak.
  447. * @returns pointer to the parent object or nullptr if parent is expired.
  448. * */
  449. fsdk::IRefCounted* getParentObject() const noexcept {
  450. return this->getParent();
  451. }
  452. protected:
  453. /**
  454. * @brief Initialize an empty object with a parent.
  455. * @param parent the parent.
  456. * */
  457. explicit ManagedPublicObjectImpl(ParentType* parent)
  458. : ManagedRefCountedImpl<T, P>(parent) {}
  459. };
  460. }