| /* |
| * Copyright 2021 Rive |
| */ |
| |
| #ifndef _RIVE_REFCNT_HPP_ |
| #define _RIVE_REFCNT_HPP_ |
| |
| #include "rive/rive_types.hpp" |
| |
| #include <atomic> |
| #include <cstddef> |
| #include <type_traits> |
| #include <utility> |
| |
| /* |
| * RefCnt : Threadsafe shared pointer baseclass. |
| * |
| * The reference count is set to one in the constructor, and goes up on every call to ref(), |
| * and down on every call to unref(). When a call to unref() brings the counter to 0, |
| * delete is called on the object. |
| * |
| * rcp : template wrapper for subclasses of RefCnt, to manage assignment and parameter passing |
| * to safely keep track of shared ownership. |
| * |
| * Both of these inspired by Skia's SkRefCnt and sk_sp |
| */ |
| |
| namespace rive { |
| |
| class RefCnt { |
| public: |
| RefCnt() : m_refcnt(1) {} |
| |
| virtual ~RefCnt() { assert(this->debugging_refcnt() == 1); } |
| |
| void ref() const { (void)m_refcnt.fetch_add(+1, std::memory_order_relaxed); } |
| |
| void unref() const { |
| if (1 == m_refcnt.fetch_add(-1, std::memory_order_acq_rel)) { |
| #ifndef NDEBUG |
| // we restore the "1" in debug builds just to make our destructor happy |
| (void)m_refcnt.fetch_add(+1, std::memory_order_relaxed); |
| #endif |
| delete this; |
| } |
| } |
| |
| // not reliable in actual threaded scenarios, but useful (perhaps) for debugging |
| int32_t debugging_refcnt() const { return m_refcnt.load(std::memory_order_relaxed); } |
| |
| private: |
| // mutable, so can be changed even on a const object |
| mutable std::atomic<int32_t> m_refcnt; |
| |
| RefCnt(RefCnt&&) = delete; |
| RefCnt(const RefCnt&) = delete; |
| RefCnt& operator=(RefCnt&&) = delete; |
| RefCnt& operator=(const RefCnt&) = delete; |
| }; |
| |
| template <typename T> static inline T* safe_ref(T* obj) { |
| if (obj) { |
| obj->ref(); |
| } |
| return obj; |
| } |
| |
| template <typename T> static inline void safe_unref(T* obj) { |
| if (obj) { |
| obj->unref(); |
| } |
| } |
| |
| // rcp : smart point template for holding subclasses of RefCnt |
| |
| template <typename T> class rcp { |
| public: |
| constexpr rcp() : m_ptr(nullptr) {} |
| constexpr rcp(std::nullptr_t) : m_ptr(nullptr) {} |
| explicit rcp(T* ptr) : m_ptr(ptr) {} |
| |
| rcp(const rcp<T>& other) : m_ptr(safe_ref(other.get())) {} |
| rcp(rcp<T>&& other) : m_ptr(other.release()) {} |
| |
| /** |
| * Calls unref() on the underlying object pointer. |
| */ |
| ~rcp() { safe_unref(m_ptr); } |
| |
| rcp<T>& operator=(std::nullptr_t) { |
| this->reset(); |
| return *this; |
| } |
| |
| rcp<T>& operator=(const rcp<T>& other) { |
| if (this != &other) { |
| this->reset(safe_ref(other.get())); |
| } |
| return *this; |
| } |
| |
| rcp<T>& operator=(rcp<T>&& other) { |
| this->reset(other.release()); |
| return *this; |
| } |
| |
| T& operator*() const { |
| assert(this->get() != nullptr); |
| return *this->get(); |
| } |
| |
| explicit operator bool() const { return this->get() != nullptr; } |
| |
| T* get() const { return m_ptr; } |
| T* operator->() const { return m_ptr; } |
| |
| // Unrefs the current pointer, and accepts the new pointer, but |
| // DOES NOT increment ownership of the new pointer. |
| void reset(T* ptr = nullptr) { |
| // Calling m_ptr->unref() may call this->~() or this->reset(T*). |
| // http://wg21.cmeerw.net/lwg/issue998 |
| // http://wg21.cmeerw.net/lwg/issue2262 |
| T* oldPtr = m_ptr; |
| m_ptr = ptr; |
| safe_unref(oldPtr); |
| } |
| |
| // This returns the bare point WITHOUT CHANGING ITS REFCNT, but removes it |
| // from this object, so the caller must manually manage its count. |
| T* release() { |
| T* ptr = m_ptr; |
| m_ptr = nullptr; |
| return ptr; |
| } |
| |
| void swap(rcp<T>& other) { std::swap(m_ptr, other.m_ptr); } |
| |
| private: |
| T* m_ptr; |
| }; |
| |
| template <typename T> inline void swap(rcp<T>& a, rcp<T>& b) { a.swap(b); } |
| |
| // == variants |
| |
| template <typename T> inline bool operator==(const rcp<T>& a, std::nullptr_t) { return !a; } |
| template <typename T> inline bool operator==(std::nullptr_t, const rcp<T>& b) { return !b; } |
| template <typename T, typename U> inline bool operator==(const rcp<T>& a, const rcp<U>& b) { |
| return a.get() == b.get(); |
| } |
| |
| // != variants |
| |
| template <typename T> inline bool operator!=(const rcp<T>& a, std::nullptr_t) { |
| return static_cast<bool>(a); |
| } |
| template <typename T> inline bool operator!=(std::nullptr_t, const rcp<T>& b) { |
| return static_cast<bool>(b); |
| } |
| template <typename T, typename U> inline bool operator!=(const rcp<T>& a, const rcp<U>& b) { |
| return a.get() != b.get(); |
| } |
| |
| } // namespace rive |
| |
| #endif |