aboutsummaryrefslogtreecommitdiff
path: root/src/zenbase/include
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenbase/include')
-rw-r--r--src/zenbase/include/zenbase/atomic.h74
-rw-r--r--src/zenbase/include/zenbase/refcount.h157
2 files changed, 62 insertions, 169 deletions
diff --git a/src/zenbase/include/zenbase/atomic.h b/src/zenbase/include/zenbase/atomic.h
deleted file mode 100644
index 4ad7962cf..000000000
--- a/src/zenbase/include/zenbase/atomic.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#pragma once
-
-#include <zenbase/zenbase.h>
-
-#if ZEN_COMPILER_MSC
-# include <intrin.h>
-#else
-# include <atomic>
-#endif
-
-#include <cinttypes>
-
-namespace zen {
-
-inline uint32_t
-AtomicIncrement(volatile uint32_t& value)
-{
-#if ZEN_COMPILER_MSC
- return _InterlockedIncrement((long volatile*)&value);
-#else
- return ((std::atomic<uint32_t>*)(&value))->fetch_add(1, std::memory_order_seq_cst) + 1;
-#endif
-}
-inline uint32_t
-AtomicDecrement(volatile uint32_t& value)
-{
-#if ZEN_COMPILER_MSC
- return _InterlockedDecrement((long volatile*)&value);
-#else
- return ((std::atomic<uint32_t>*)(&value))->fetch_sub(1, std::memory_order_seq_cst) - 1;
-#endif
-}
-
-inline uint64_t
-AtomicIncrement(volatile uint64_t& value)
-{
-#if ZEN_COMPILER_MSC
- return _InterlockedIncrement64((__int64 volatile*)&value);
-#else
- return ((std::atomic<uint64_t>*)(&value))->fetch_add(1, std::memory_order_seq_cst) + 1;
-#endif
-}
-inline uint64_t
-AtomicDecrement(volatile uint64_t& value)
-{
-#if ZEN_COMPILER_MSC
- return _InterlockedDecrement64((__int64 volatile*)&value);
-#else
- return ((std::atomic<uint64_t>*)(&value))->fetch_sub(1, std::memory_order_seq_cst) - 1;
-#endif
-}
-
-inline uint32_t
-AtomicAdd(volatile uint32_t& value, uint32_t amount)
-{
-#if ZEN_COMPILER_MSC
- return _InterlockedExchangeAdd((long volatile*)&value, amount);
-#else
- return ((std::atomic<uint32_t>*)(&value))->fetch_add(amount, std::memory_order_seq_cst);
-#endif
-}
-inline uint64_t
-AtomicAdd(volatile uint64_t& value, uint64_t amount)
-{
-#if ZEN_COMPILER_MSC
- return _InterlockedExchangeAdd64((__int64 volatile*)&value, amount);
-#else
- return ((std::atomic<uint64_t>*)(&value))->fetch_add(amount, std::memory_order_seq_cst);
-#endif
-}
-
-} // namespace zen
diff --git a/src/zenbase/include/zenbase/refcount.h b/src/zenbase/include/zenbase/refcount.h
index 08bc6ae54..0da78ad91 100644
--- a/src/zenbase/include/zenbase/refcount.h
+++ b/src/zenbase/include/zenbase/refcount.h
@@ -1,9 +1,9 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
-#include <zenbase/atomic.h>
#include <zenbase/concepts.h>
+#include <atomic>
#include <compare>
namespace zen {
@@ -13,6 +13,13 @@ namespace zen {
*
* When the reference count reaches zero, the object deletes itself. This class relies
* on having a virtual destructor to ensure proper cleanup of derived classes.
+ *
+ * Release() is marked noexcept. Derived destructors are expected not to throw - if a
+ * derived destructor does throw, the exception crosses the noexcept boundary and the
+ * program terminates via std::terminate. This matches C++'s default destructor
+ * behaviour (destructors are implicitly noexcept) but is spelled out explicitly here
+ * because the delete happens inside Release() rather than at the call site, so the
+ * terminate point is not visually obvious.
*/
class RefCounted
{
@@ -20,10 +27,15 @@ public:
RefCounted() = default;
virtual ~RefCounted() = default;
- inline uint32_t AddRef() const noexcept { return AtomicIncrement(const_cast<RefCounted*>(this)->m_RefCount); }
- inline uint32_t Release() const
+ // AddRef uses relaxed ordering: a thread can only add a reference to an object it
+ // already has a reference to, so there is nothing that needs to synchronize here.
+ // Release uses acq_rel so that (a) all prior modifications of the object made on
+ // other threads happen-before the destructor, and (b) the ref-count decrement is
+ // visible to any thread that observes the new count.
+ inline uint32_t AddRef() const noexcept { return m_RefCount.fetch_add(1, std::memory_order_relaxed) + 1; }
+ inline uint32_t Release() const noexcept
{
- const uint32_t RefCount = AtomicDecrement(const_cast<RefCounted*>(this)->m_RefCount);
+ const uint32_t RefCount = m_RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
if (RefCount == 0)
{
delete this;
@@ -39,10 +51,12 @@ public:
RefCounted& operator=(RefCounted&&) = delete;
protected:
- inline uint32_t RefCount() const { return m_RefCount; }
+ // Diagnostic accessor: relaxed load is fine because the returned value is already
+ // stale the moment it is observed, so no extra ordering would make it more reliable.
+ inline uint32_t RefCount() const noexcept { return m_RefCount.load(std::memory_order_relaxed); }
private:
- uint32_t m_RefCount = 0;
+ mutable std::atomic<uint32_t> m_RefCount = 0;
};
/**
@@ -52,6 +66,10 @@ private:
* It has no virtual destructor, so it's important that you either don't derive from it further,
* or ensure that the derived class has a virtual destructor.
*
+ * As with RefCounted, Release() is noexcept and the derived destructor must not throw.
+ * A throwing destructor will cause std::terminate to be called when the refcount hits
+ * zero.
+ *
* This class is useful when you want to avoid adding a vtable to a class just to implement
* reference counting.
*/
@@ -60,15 +78,17 @@ template<typename T>
class TRefCounted
{
public:
- TRefCounted() = default;
- ~TRefCounted() = default;
+ TRefCounted() = default;
- inline uint32_t AddRef() const noexcept { return AtomicIncrement(const_cast<TRefCounted<T>*>(this)->m_RefCount); }
- inline uint32_t Release() const
+ // See RefCounted::AddRef/Release for ordering rationale.
+ inline uint32_t AddRef() const noexcept { return m_RefCount.fetch_add(1, std::memory_order_relaxed) + 1; }
+ inline uint32_t Release() const noexcept
{
- const uint32_t RefCount = AtomicDecrement(const_cast<TRefCounted<T>*>(this)->m_RefCount);
+ const uint32_t RefCount = m_RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
if (RefCount == 0)
{
+ // DeleteThis may be overridden as a non-const member in derived types,
+ // so we cast away const to support both signatures.
const_cast<T*>(static_cast<const T*>(this))->DeleteThis();
}
return RefCount;
@@ -82,92 +102,21 @@ public:
TRefCounted& operator=(TRefCounted&&) = delete;
protected:
- inline uint32_t RefCount() const { return m_RefCount; }
-
- void DeleteThis() const noexcept { delete static_cast<const T*>(this); }
-
-private:
- uint32_t m_RefCount = 0;
-};
-
-/**
- * Smart pointer for classes derived from RefCounted
- */
-
-template<class T>
-class RefPtr
-{
-public:
- inline RefPtr() = default;
- inline RefPtr(const RefPtr& Rhs) : m_Ref(Rhs.m_Ref) { m_Ref && m_Ref->AddRef(); }
- inline RefPtr(T* Ptr) : m_Ref(Ptr) { m_Ref && m_Ref->AddRef(); }
- inline ~RefPtr() { m_Ref && m_Ref->Release(); }
-
- [[nodiscard]] inline bool IsNull() const { return m_Ref == nullptr; }
- inline explicit operator bool() const { return m_Ref != nullptr; }
- inline operator T*() const { return m_Ref; }
- inline T* operator->() const { return m_Ref; }
+ // Non-virtual: destruction goes through DeleteThis(), which deletes the derived type.
+ // Protected so that external callers cannot `delete` through a TRefCounted<T>* and slice.
+ ~TRefCounted() = default;
- inline std::strong_ordering operator<=>(const RefPtr& Rhs) const = default;
+ // Diagnostic accessor: see RefCounted::RefCount for ordering rationale.
+ inline uint32_t RefCount() const noexcept { return m_RefCount.load(std::memory_order_relaxed); }
- inline RefPtr& operator=(T* Rhs)
- {
- Rhs && Rhs->AddRef();
- m_Ref && m_Ref->Release();
- m_Ref = Rhs;
- return *this;
- }
- inline RefPtr& operator=(const RefPtr& Rhs)
- {
- if (&Rhs != this)
- {
- Rhs && Rhs->AddRef();
- m_Ref && m_Ref->Release();
- m_Ref = Rhs.m_Ref;
- }
- return *this;
- }
- inline RefPtr& operator=(RefPtr&& Rhs) noexcept
- {
- if (&Rhs != this)
- {
- m_Ref && m_Ref->Release();
- m_Ref = Rhs.m_Ref;
- Rhs.m_Ref = nullptr;
- }
- return *this;
- }
- template<typename OtherType>
- inline RefPtr& operator=(RefPtr<OtherType>&& Rhs) noexcept
- {
- if ((RefPtr*)&Rhs != this)
- {
- m_Ref && m_Ref->Release();
- m_Ref = Rhs.m_Ref;
- Rhs.m_Ref = nullptr;
- }
- return *this;
- }
- inline RefPtr(RefPtr&& Rhs) noexcept : m_Ref(Rhs.m_Ref) { Rhs.m_Ref = nullptr; }
- template<typename OtherType>
- explicit inline RefPtr(RefPtr<OtherType>&& Rhs) noexcept : m_Ref(Rhs.m_Ref)
- {
- Rhs.m_Ref = nullptr;
- }
-
- inline void Swap(RefPtr& Rhs) noexcept { std::swap(m_Ref, Rhs.m_Ref); }
+ void DeleteThis() const noexcept { delete static_cast<const T*>(this); }
private:
- T* m_Ref = nullptr;
- template<typename U>
- friend class RefPtr;
+ mutable std::atomic<uint32_t> m_RefCount = 0;
};
/**
- * Smart pointer for classes derived from RefCounted
- *
- * This variant does not decay to a raw pointer
- *
+ * Smart pointer for classes derived from RefCounted (or TRefCounted)
*/
template<class T>
@@ -177,12 +126,16 @@ public:
inline Ref() = default;
inline Ref(Ref&& Rhs) noexcept : m_Ref(Rhs.m_Ref) { Rhs.m_Ref = nullptr; }
inline Ref(const Ref& Rhs) noexcept : m_Ref(Rhs.m_Ref) { m_Ref && m_Ref->AddRef(); }
- inline explicit Ref(T* Ptr) : m_Ref(Ptr) { m_Ref && m_Ref->AddRef(); }
- inline ~Ref() { m_Ref && m_Ref->Release(); }
+ inline explicit Ref(T* Ptr) noexcept : m_Ref(Ptr) { m_Ref && m_Ref->AddRef(); }
+ inline ~Ref() noexcept { m_Ref && m_Ref->Release(); }
template<typename DerivedType>
requires DerivedFrom<DerivedType, T>
- inline Ref(const Ref<DerivedType>& Rhs) : Ref(Rhs.m_Ref) {}
+ inline Ref(const Ref<DerivedType>& Rhs) noexcept : Ref(Rhs.m_Ref) {}
+
+ template<typename DerivedType>
+ requires DerivedFrom<DerivedType, T>
+ inline Ref(Ref<DerivedType>&& Rhs) noexcept : m_Ref(Rhs.m_Ref) { Rhs.m_Ref = nullptr; }
[[nodiscard]] inline bool IsNull() const { return m_Ref == nullptr; }
inline explicit operator bool() const { return m_Ref != nullptr; }
@@ -192,14 +145,14 @@ public:
inline std::strong_ordering operator<=>(const Ref& Rhs) const = default;
- inline Ref& operator=(T* Rhs)
+ inline Ref& operator=(T* Rhs) noexcept
{
Rhs && Rhs->AddRef();
m_Ref && m_Ref->Release();
m_Ref = Rhs;
return *this;
}
- inline Ref& operator=(const Ref& Rhs)
+ inline Ref& operator=(const Ref& Rhs) noexcept
{
if (&Rhs != this)
{
@@ -219,6 +172,20 @@ public:
}
return *this;
}
+ template<typename DerivedType>
+ requires DerivedFrom<DerivedType, T>
+ inline Ref& operator=(Ref<DerivedType>&& Rhs) noexcept
+ {
+ if ((Ref*)&Rhs != this)
+ {
+ m_Ref && m_Ref->Release();
+ m_Ref = Rhs.m_Ref;
+ Rhs.m_Ref = nullptr;
+ }
+ return *this;
+ }
+
+ inline void Swap(Ref& Rhs) noexcept { std::swap(m_Ref, Rhs.m_Ref); }
private:
T* m_Ref = nullptr;