summaryrefslogtreecommitdiffstats
path: root/Common/include
diff options
context:
space:
mode:
authorEgor Yusov <egor.yusov@gmail.com>2017-11-13 02:54:50 +0000
committerEgor Yusov <egor.yusov@gmail.com>2017-11-13 02:54:50 +0000
commit8e24a0919d30f939140e776bc5e64bd77767e99c (patch)
treebfa1c37385cafe7d6c192ed0c8eb936aa828cb5a /Common/include
parentMerge from dev branch (diff)
downloadDiligentCore-8e24a0919d30f939140e776bc5e64bd77767e99c.tar.gz
DiligentCore-8e24a0919d30f939140e776bc5e64bd77767e99c.zip
Updated to Diligent Engine 2.1
Diffstat (limited to 'Common/include')
-rw-r--r--Common/include/AdaptiveFixedBlockAllocator.h2
-rw-r--r--Common/include/BasicFileStream.h9
-rw-r--r--Common/include/DataBlobImpl.h6
-rw-r--r--Common/include/FixedBlockMemoryAllocator.h93
-rw-r--r--Common/include/HashUtils.h2
-rw-r--r--Common/include/LockHelper.h59
-rw-r--r--Common/include/ObjectBase.h10
-rw-r--r--Common/include/RefCntAutoPtr.h28
-rw-r--r--Common/include/RefCountedObjectImpl.h900
-rw-r--r--Common/include/StringDataBlobImpl.h70
10 files changed, 779 insertions, 400 deletions
diff --git a/Common/include/AdaptiveFixedBlockAllocator.h b/Common/include/AdaptiveFixedBlockAllocator.h
index 51396f14..3db6939b 100644
--- a/Common/include/AdaptiveFixedBlockAllocator.h
+++ b/Common/include/AdaptiveFixedBlockAllocator.h
@@ -1,4 +1,4 @@
-/* Copyright 2015 Egor Yusov
+/* Copyright 2015-2017 Egor Yusov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/Common/include/BasicFileStream.h b/Common/include/BasicFileStream.h
index 306c4225..42e29d9f 100644
--- a/Common/include/BasicFileStream.h
+++ b/Common/include/BasicFileStream.h
@@ -36,13 +36,14 @@ namespace Diligent
{
/// Basic file stream implementation
-class BasicFileStream : public ObjectBase<IFileStream, IMemoryAllocator>
+class BasicFileStream : public ObjectBase<IFileStream>
{
public:
- typedef ObjectBase<IFileStream, IMemoryAllocator> TBase;
+ typedef ObjectBase<IFileStream> TBase;
- BasicFileStream(const Diligent::Char *Path,
- EFileAccessMode Access = EFileAccessMode::Read);
+ BasicFileStream(IReferenceCounters *pRefCounters,
+ const Diligent::Char *Path,
+ EFileAccessMode Access = EFileAccessMode::Read);
virtual void QueryInterface( const Diligent::INTERFACE_ID &IID, IObject **ppInterface )override;
diff --git a/Common/include/DataBlobImpl.h b/Common/include/DataBlobImpl.h
index 5b10489e..3a1ad35f 100644
--- a/Common/include/DataBlobImpl.h
+++ b/Common/include/DataBlobImpl.h
@@ -35,10 +35,12 @@ namespace Diligent
{
/// Base interface for a file stream
-class DataBlobImpl : public Diligent::ObjectBase<IDataBlob, IMemoryAllocator>
+class DataBlobImpl : public Diligent::ObjectBase<IDataBlob>
{
public:
- typedef Diligent::ObjectBase<IDataBlob, IMemoryAllocator> TBase;
+ typedef Diligent::ObjectBase<IDataBlob> TBase;
+
+ DataBlobImpl( IReferenceCounters *pRefCounters, size_t InitialSize = 0 );
virtual void QueryInterface( const Diligent::INTERFACE_ID &IID, IObject **ppInterface )override;
diff --git a/Common/include/FixedBlockMemoryAllocator.h b/Common/include/FixedBlockMemoryAllocator.h
index 66013060..93748c5b 100644
--- a/Common/include/FixedBlockMemoryAllocator.h
+++ b/Common/include/FixedBlockMemoryAllocator.h
@@ -32,6 +32,8 @@
#include <vector>
#include "MemoryAllocator.h"
#include "STDAllocator.h"
+#include "Errors.h"
+
namespace Diligent
{
@@ -217,8 +219,93 @@ private:
IMemoryAllocator &m_RawMemoryAllocator;
size_t m_BlockSize;
Uint32 m_NumBlocksInPage;
-
- //Uint8 *tmpLargeBuffer, *tmpCurrPtr;
};
-
+
+IMemoryAllocator& GetRawAllocator();
+
+template<typename ObjectType>
+class ObjectPool
+{
+public:
+ static void SetRawAllocator(IMemoryAllocator &Allocator)
+ {
+#ifdef _DEBUG
+ if(m_bPoolInitialized && m_pRawAllocator != &Allocator)
+ {
+ LOG_WARNING_MESSAGE("Setting pool raw allocator after the pool has been initialized has no effect")
+ }
+#endif
+ m_pRawAllocator = &Allocator;
+ }
+ static void SetPageSize(Uint32 NumAllocationsInPage)
+ {
+#ifdef _DEBUG
+ if(m_bPoolInitialized && m_NumAllocationsInPage != NumAllocationsInPage)
+ {
+ LOG_WARNING_MESSAGE("Setting pool page size after the pool has been initialized has no effect")
+ }
+#endif
+ m_NumAllocationsInPage = NumAllocationsInPage;
+ }
+ static ObjectPool& GetPool()
+ {
+ static ObjectPool ThePool;
+#ifdef _DEBUG
+ m_bPoolInitialized = true;
+#endif
+ return ThePool;
+ }
+
+ template<typename ... CtorArgTypes>
+ ObjectType* NewObject(const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber, CtorArgTypes&& ... CtorArgs)
+ {
+ void *pRawMem = m_FixedBlockAlloctor.Allocate(sizeof(ObjectType), dbgDescription, dbgFileName, dbgLineNumber);
+ try
+ {
+ return new(pRawMem) ObjectType(std::forward<CtorArgTypes>(CtorArgs)...);
+ }
+ catch (...)
+ {
+ m_FixedBlockAlloctor.Free(pRawMem);
+ return nullptr;
+ }
+ }
+
+ void Destroy(ObjectType* pObj)
+ {
+ if(pObj != nullptr)
+ {
+ pObj->~ObjectType();
+ m_FixedBlockAlloctor.Free(pObj);
+ }
+ }
+
+private:
+ static Uint32 m_NumAllocationsInPage;
+ static IMemoryAllocator *m_pRawAllocator;
+
+ ObjectPool() :
+ m_FixedBlockAlloctor(m_pRawAllocator ? *m_pRawAllocator : GetRawAllocator(), sizeof(ObjectType), m_NumAllocationsInPage)
+ {}
+#ifdef _DEBUG
+ static bool m_bPoolInitialized;
+#endif
+ FixedBlockMemoryAllocator m_FixedBlockAlloctor;
+};
+template<typename ObjectType>
+Uint32 ObjectPool<ObjectType>::m_NumAllocationsInPage = 64;
+
+template<typename ObjectType>
+IMemoryAllocator* ObjectPool<ObjectType>::m_pRawAllocator = nullptr;
+
+#ifdef _DEBUG
+template<typename ObjectType>
+bool ObjectPool<ObjectType>::m_bPoolInitialized = false;
+#endif
+
+#define SET_POOL_RAW_ALLOCATOR(ObjectType, Allocator)ObjectPool<ObjectType>::SetRawAllocator(Allocator)
+#define SET_POOL_PAGE_SIZE(ObjectType, NumAllocationsInPage)ObjectPool<ObjectType>::SetPageSize(NumAllocationsInPage)
+#define NEW_POOL_OBJECT(ObjectType, Desc, ...)ObjectPool<ObjectType>::GetPool().NewObject(Desc, __FILE__, __LINE__, ##__VA_ARGS__)
+#define DESTROY_POOL_OBJECT(pObject)ObjectPool< std::remove_reference<decltype(*pObject)>::type >::GetPool().Destroy(pObject)
+
}
diff --git a/Common/include/HashUtils.h b/Common/include/HashUtils.h
index cf883eb9..8006d944 100644
--- a/Common/include/HashUtils.h
+++ b/Common/include/HashUtils.h
@@ -26,6 +26,8 @@
#include <functional>
#include <memory>
+#include "Errors.h"
+
#define LOG_HASH_CONFLICTS 1
namespace Diligent
diff --git a/Common/include/LockHelper.h b/Common/include/LockHelper.h
index 47e6b6de..06e1b596 100644
--- a/Common/include/LockHelper.h
+++ b/Common/include/LockHelper.h
@@ -24,6 +24,9 @@
#pragma once
#include <thread>
+#include <mutex>
+#include <condition_variable>
+
#include "Atomics.h"
namespace ThreadingTools
@@ -128,4 +131,60 @@ private:
const LockHelper& operator = ( const LockHelper &LockHelper );
};
+class Signal
+{
+public:
+ Signal() {}
+
+ // http://en.cppreference.com/w/cpp/thread/condition_variable
+ void Trigger()
+ {
+ // The thread that intends to modify the variable has to
+ // * acquire a std::mutex (typically via std::lock_guard)
+ // * perform the modification while the lock is held
+ // * execute notify_one or notify_all on the std::condition_variable (the lock does not need to be held for notification)
+ {
+ // std::condition_variable works only with std::unique_lock<std::mutex>
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ m_bIsTriggered = true;
+ }
+ // Unlocking is done before notifying, to avoid waking up the waiting
+ // thread only to block again (see notify_one for details)
+ m_CondVar.notify_one();
+ }
+
+ void Wait()
+ {
+ // Any thread that intends to wait on std::condition_variable has to
+ // * acquire a std::unique_lock<std::mutex>, on the SAME MUTEX as used to protect the shared variable
+ // * execute wait, wait_for, or wait_until. The wait operations atomically release the mutex
+ // and suspend the execution of the thread.
+ // * When the condition variable is notified, a timeout expires, or a spurious wakeup occurs,
+ // the thread is awakened, and the mutex is atomically reacquired:
+ // - The thread should then check the condition and resume waiting if the wake up was spurious.
+ std::unique_lock<std::mutex> Lock(m_Mutex);
+ if (!m_bIsTriggered)
+ {
+ m_CondVar.wait(Lock, [&] {return m_bIsTriggered; });
+ }
+ }
+
+ void Reset()
+ {
+ std::lock_guard<std::mutex> Lock(m_Mutex);
+ m_bIsTriggered = false;
+ }
+
+ volatile bool IsTriggered()const { return m_bIsTriggered; }
+
+private:
+
+ std::mutex m_Mutex;
+ std::condition_variable m_CondVar;
+ volatile bool m_bIsTriggered = false;
+
+ Signal(const Signal&) = delete;
+ Signal& operator = (const Signal&) = delete;
+};
+
}
diff --git a/Common/include/ObjectBase.h b/Common/include/ObjectBase.h
index 9be99f24..14e7ae8c 100644
--- a/Common/include/ObjectBase.h
+++ b/Common/include/ObjectBase.h
@@ -53,17 +53,17 @@ namespace Diligent
IMPLEMENT_QUERY_INTERFACE_BODY(InterfaceID, ParentClassName)
#define IMPLEMENT_QUERY_INTERFACE_IN_PLACE(InterfaceID, ParentClassName) \
- virtual void QueryInterface(const Diligent::INTERFACE_ID &IID, IObject **ppInterface) \
+ virtual void QueryInterface(const Diligent::INTERFACE_ID &IID, IObject **ppInterface)override \
IMPLEMENT_QUERY_INTERFACE_BODY(InterfaceID, ParentClassName)
/// Template class implementing base functionality for an object
-template<typename BaseInterface, typename TObjectAllocator = IMemoryAllocator>
-class ObjectBase : public RefCountedObject<BaseInterface, TObjectAllocator>
+template<typename BaseInterface>
+class ObjectBase : public RefCountedObject<BaseInterface>
{
public:
- ObjectBase(IObject *pOwner = nullptr, TObjectAllocator *pObjAllocator = nullptr) :
- RefCountedObject<BaseInterface, TObjectAllocator>( pOwner, pObjAllocator )
+ ObjectBase(IReferenceCounters *pRefCounters) :
+ RefCountedObject<BaseInterface>( pRefCounters )
{}
virtual void QueryInterface( const Diligent::INTERFACE_ID &IID, IObject **ppInterface )
diff --git a/Common/include/RefCntAutoPtr.h b/Common/include/RefCntAutoPtr.h
index 2d99699e..3408be17 100644
--- a/Common/include/RefCntAutoPtr.h
+++ b/Common/include/RefCntAutoPtr.h
@@ -27,7 +27,7 @@
#include "LockHelper.h"
#include "Atomics.h"
#include "ValidatedCast.h"
-#include "ReferenceCounters.h"
+#include "RefCountedObjectImpl.h"
#include "Object.h"
namespace Diligent
@@ -182,19 +182,8 @@ public:
operator T* () { return RawPtr(); }
operator const T* ()const { return RawPtr(); }
-
- class BlockAddRefRelease : public T
- {
- private:
- // Note that the null pointer constant nullptr or any other value of type std::nullptr_t
- // cannot be converted to a pointer with reinterpret_cast: implicit conversion or
- // static_cast should be used for this purpose.
- virtual decltype( static_cast<T*>(nullptr)->AddRef() ) AddRef()override = 0;
- virtual decltype( static_cast<T*>(nullptr)->Release() ) Release()override = 0;
- };
-
- BlockAddRefRelease* operator -> () { return static_cast<BlockAddRefRelease*> (m_pObject); }
- const BlockAddRefRelease* operator -> ()const{ return static_cast<BlockAddRefRelease*> (m_pObject); }
+ T* operator -> () { return m_pObject; }
+ const T* operator -> ()const{ return m_pObject; }
private:
// Note that the DoublePtrHelper is a private class, and can be created only by RefCntWeakPtr
@@ -249,6 +238,9 @@ public:
return DoublePtrHelper(*this);
}
+ T** GetRawDblPtr() {return &m_pObject;}
+ const T** GetRawDblPtr()const{return &m_pObject;}
+
private:
T *m_pObject;
};
@@ -264,7 +256,7 @@ public:
{
if( m_pObject )
{
- m_pRefCounters = m_pObject->GetReferenceCounters();
+ m_pRefCounters = ValidatedCast<RefCountersImpl>( m_pObject->GetReferenceCounters() );
m_pRefCounters->AddWeakRef();
}
}
@@ -292,7 +284,7 @@ public:
explicit RefCntWeakPtr(RefCntAutoPtr<T>& AutoPtr) :
m_pObject( static_cast<T*>(AutoPtr) ),
- m_pRefCounters(AutoPtr ? AutoPtr->GetReferenceCounters() : nullptr)
+ m_pRefCounters(AutoPtr ? ValidatedCast<RefCountersImpl>( AutoPtr->GetReferenceCounters() ) : nullptr)
{
if( m_pRefCounters )
m_pRefCounters->AddWeakRef();
@@ -333,7 +325,7 @@ public:
{
Release();
m_pObject = static_cast<T*>( AutoPtr );
- m_pRefCounters = m_pObject ? m_pObject->GetReferenceCounters() : nullptr;
+ m_pRefCounters = m_pObject ? ValidatedCast<RefCountersImpl>( m_pObject->GetReferenceCounters() ) : nullptr;
if( m_pRefCounters )
m_pRefCounters->AddWeakRef();
return *this;
@@ -387,7 +379,7 @@ public:
bool operator != (const RefCntWeakPtr& Ptr)const{return m_pRefCounters != Ptr.m_pRefCounters;}
protected:
- Diligent::IReferenceCounters *m_pRefCounters;
+ RefCountersImpl *m_pRefCounters;
// We need to store raw pointer to object itself,
// because if the object is owned by another object,
// m_pRefCounters->GetObject( &pObj ) will return
diff --git a/Common/include/RefCountedObjectImpl.h b/Common/include/RefCountedObjectImpl.h
index d3c8d38c..5960a5e5 100644
--- a/Common/include/RefCountedObjectImpl.h
+++ b/Common/include/RefCountedObjectImpl.h
@@ -36,38 +36,456 @@
namespace Diligent
{
-class IMemoryAllocator;
-/// Base class for reference counting objects
-template<typename Base, typename TObjectAllocator = IMemoryAllocator>
-class RefCountedObject : public Base
+// This class controls the lifetime of a refcounted object
+class RefCountersImpl : public IReferenceCounters
{
public:
- RefCountedObject(IObject *pOwner = nullptr, TObjectAllocator *pObjAllocator = nullptr) :
- m_pRefCounters(nullptr),
- m_pAllocator(pObjAllocator)
+ inline virtual Atomics::Long AddStrongRef()override final
+ {
+ VERIFY( m_ObjectState == ObjectState::Alive, "Attempting to increment strong reference counter for a destroyed or not itialized object!" );
+ VERIFY( m_ObjectWrapperBuffer[0] != 0 && m_ObjectWrapperBuffer[1] != 0, "Object wrapper is not initialized")
+ return Atomics::AtomicIncrement(m_lNumStrongReferences);
+ }
+
+ inline virtual Atomics::Long ReleaseStrongRef()override final
{
- if( pOwner )
+ VERIFY( m_ObjectState == ObjectState::Alive, "Attempting to decrement strong reference counter for an object that is not alive" )
+ VERIFY( m_ObjectWrapperBuffer[0] != 0 && m_ObjectWrapperBuffer[1] != 0, "Object wrapper is not initialized")
+
+ // Decrement strong reference counter without acquiring the lock.
+ auto RefCount = Atomics::AtomicDecrement(m_lNumStrongReferences);
+ VERIFY( RefCount >= 0, "Inconsistent call to ReleaseStrongRef()" );
+ if( RefCount == 0 )
{
- auto *pRefCounters = pOwner->GetReferenceCounters();
- VERIFY(pRefCounters, "Reference counters are not initialized in the owner object");
- m_pRefCounters = ValidatedCast<IReferenceCounters>( pRefCounters );
+ // Since RefCount==0, there are no more strong references and the only place
+ // where strong ref counter can be incremented is from GetObject().
+
+ // If several threads were allowed to get to this point, there would
+ // be serious risk that <this> had already been destroyed and m_LockFlag expired.
+ // Consider the following scenario:
+ // |
+ // This thread | Another thread
+ // |
+ // m_lNumStrongReferences == 1
+ // m_lNumWeakReferences == 1
+ // |
+ // 1. Decrement m_lNumStrongReferences |
+ // Read RefCount==0, no lock acquired|
+ // | 1. Run GetObject()
+ // | - acquire the lock
+ // | - increment m_lNumStrongReferences
+ // | - release the lock
+ // |
+ // | 2. Run ReleaseWeakRef()
+ // | - decrement m_lNumWeakReferences
+ // |
+ // | 3. Run ReleaseStrongRef()
+ // | - decrement m_lNumStrongReferences
+ // | - read RefCount==0
+ //
+ // Both threads will get to this point. The first one will destroy <this>
+ // The second one will read expired m_LockFlag
+
+ // IT IS CRUCIALLY IMPORTANT TO ASSURE THAT ONLY ONE THREAD WILL EVER
+ // EXECUTE THIS CODE
+
+ // The sloution is to atomically increment strong ref counter in GetObject().
+ // There are two possible scenarios depending on who first increments the counter:
+
+
+ // Scenario I
+ //
+ // This thread | Another thread - GetObject() | One more thread - GetObject()
+ // | |
+ // m_lNumStrongReferences == 1 |
+ // | |
+ // | 1. Acquire the lock |
+ // 1. Decrement m_lNumStrongReferences | | 1. Wait for the lock
+ // 2. Read RefCount==0 | 2. Increment m_lNumStrongReferences |
+ // 3. Start destroying the object | 3. Read StrongRefCnt == 1 |
+ // 4. Wait for the lock | 4. DO NOT return the reference |
+ // | to the object |
+ // | 5. Decrement m_lNumStrongReferences |
+ // _ _ _ _ _ _ _ _ _ _ _ _ _| 6. Release the lock _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ _ _ _ _ _
+ // | | 2. Acquire the lock
+ // | | 3. Increment m_lNumStrongReferences
+ // | | 4. Read StrongRefCnt == 1
+ // | | 5. DO NOT return the reference
+ // | | to the object
+ // | | 6. Decrement m_lNumStrongReferences
+ // _ _ _ _ _ _ _ _ _ _ _ _ | _ _ _ _ _ _ _ _ _ _ _ _ _ _ | _ 7. Release the lock _ _ _ _ _ _
+ // 5. Acquire the lock | |
+ // - m_lNumStrongReferences==0 | |
+ // 6. DESTROY the object | |
+ // | |
+
+ // GetObject() MUST BE SERIALIZED for this to work properly!
+
+
+ // Scenario II
+ //
+ // This thread | Another thread - GetObject()
+ // |
+ // m_lNumStrongReferences == 1
+ // |
+ // | 1. Acquire the lock
+ // | 2. Increment m_lNumStrongReferences
+ // 1. Decrement m_lNumStrongReferences |
+ // 2. Read RefCount>0 |
+ // 3. DO NOT destroy the object | 3. Read StrongRefCnt > 1 (while m_lNumStrongReferences == 1)
+ // | 4. Return the reference to the object
+ // | - Increment m_lNumStrongReferences
+ // | 5. Decrement m_lNumStrongReferences
+
+#ifdef _DEBUG
+ Atomics::Long NumStrongRefs = m_lNumStrongReferences;
+ VERIFY( NumStrongRefs == 0 || NumStrongRefs == 1, "Num strong references (", NumStrongRefs, ") is expected to be 0 or 1" );
+#endif
+
+ // Acquire the lock.
+ ThreadingTools::LockHelper Lock(m_LockFlag);
+
+ // GetObject() first acquires the lock, and only then increments and
+ // decrements the ref counter. If it reads 1 after incremeting the counter,
+ // it does not return the reference to the object and decrements the counter.
+ // If we acquired the lock, GetObject() will not start until we are done
+ VERIFY_EXPR( m_lNumStrongReferences == 0 && m_ObjectState == ObjectState::Alive )
+
+ // Extra caution
+ if(m_lNumStrongReferences == 0 && m_ObjectState == ObjectState::Alive)
+ {
+ VERIFY(m_ObjectWrapperBuffer[0] != 0 && m_ObjectWrapperBuffer[1] != 0, "Object wrapper is not initialized")
+ // We cannot destroy the object while reference counters are locked as this will
+ // cause a deadlock in cases like this:
+ //
+ // A ==sp==> B ---wp---> A
+ //
+ // RefCounters_A.Lock();
+ // delete A{
+ // A.~dtor(){
+ // B.~dtor(){
+ // wpA.ReleaseWeakRef(){
+ // RefCounters_A.Lock(); // Deadlock
+ //
+
+ // So we copy the object wrapper and destroy the object after unlocking the
+ // reference counters
+ size_t ObjectWrapperBufferCopy[ObjectWrapperBufferSize];
+ for(size_t i=0; i < ObjectWrapperBufferSize; ++i)
+ ObjectWrapperBufferCopy[i] = m_ObjectWrapperBuffer[i];
+#ifdef _DEBUG
+ memset(m_ObjectWrapperBuffer, 0, sizeof(m_ObjectWrapperBuffer));
+#endif
+ auto *pWrapper = reinterpret_cast<ObjectWrapperBase*>(ObjectWrapperBufferCopy);
+
+ // In a multithreaded environment, reference counters object may
+ // be destroyed at any time while m_pObject->~dtor() is running.
+ // NOTE: m_pObject may not be the only object referencing m_pRefCounters.
+ // All objects that are owned by m_pObject will point to the same
+ // reference counters object.
+
+ // Note that this is the only place where m_ObjectState is
+ // modified after the ref counters object has been created
+ m_ObjectState = ObjectState::Destroyed;
+ // The object is now detached from the reference counters and it is if
+ // it was destroyed since no one can obtain access to it.
+
+
+ // It is essentially important to check the number of weak references
+ // while the object is locked. Otherwise reference counters object
+ // may be destroyed twice if ReleaseWeakRef() is executed by other thread:
+ //
+ // This thread | Another thread - ReleaseWeakRef()
+ // |
+ // 1. Decrement m_lNumStrongReferences,|
+ // m_lNumStrongReferences==0, |
+ // acquire the lock, destroy |
+ // the obj, release the lock |
+ // m_lNumWeakReferences == 1 |
+ // | 1. Aacquire the lock,
+ // | decrement m_lNumWeakReferences,
+ // | m_lNumWeakReferences == 0,
+ // | m_ObjectState == ObjectState::Destroyed
+ // |
+ // 2. Read m_lNumWeakReferences == 0 |
+ // 3. Destroy the ref counters obj | 2. Destroy the ref counters obj
+ //
+ bool bDestroyThis = m_lNumWeakReferences == 0;
+ // ReleaseWeakRef() decrements m_lNumWeakReferences, and checks it for
+ // zero only after acquiring the lock. So if m_lNumWeakReferences==0, no
+ // weak reference-related code may be running
+
+
+ // We must explicitly unlock the object now to avoid deadlocks. Also,
+ // if this is deleted, this->m_LockFlag will expire, which will cause
+ // Lock.~LockHelper() to crash
+ Lock.Unlock();
+
+ // Destroy referenced object
+ pWrapper->DestroyObject();
+
+ // Note that <this> may be destroyed here already,
+ // see comments in ~ControlledObjectType()
+ if( bDestroyThis )
+ SelfDestroy();
+ }
}
- else
+
+ return RefCount;
+ }
+
+ inline virtual Atomics::Long AddWeakRef()override final
+ {
+ return Atomics::AtomicIncrement(m_lNumWeakReferences);
+ }
+
+ inline virtual Atomics::Long ReleaseWeakRef()override final
+ {
+ // The method must be serialized!
+ ThreadingTools::LockHelper Lock(m_LockFlag);
+ // It is essentially important to check the number of weak references
+ // while holding the lock. Otherwise reference counters object
+ // may be destroyed twice if ReleaseStrongRef() is executed by other
+ // thread.
+ auto NumWeakReferences = Atomics::AtomicDecrement(m_lNumWeakReferences);
+ VERIFY( NumWeakReferences >= 0, "Inconsistent call to ReleaseWeakRef()" );
+
+ // There are two special case when we must not destroy the ref counters object even
+ // when NumWeakReferences == 0 && m_lNumStrongReferences == 0 :
+ //
+ // This thread | Another thread - ReleaseStrongRef()
+ // |
+ // 1. Lock the object |
+ // |
+ // 2. Decrement m_lNumWeakReferences, | 1. Decrement m_lNumStrongReferences,
+ // m_lNumWeakReferences==0 | RefCount == 0
+ // |
+ // | 2. Start waiting for the lock to destroy
+ // | the object, m_ObjectState != ObjectState::Destroyed
+ // 3. Do not destroy reference |
+ // counters, unlock |
+ // | 3. Acquire the lock,
+ // | destroy the object,
+ // | read m_lNumWeakReferences==0
+ // | destroy the reference counters
+ //
+
+ // If an exception is thrown during the object construction and there is a weak pointer to the object itself,
+ // we may get to this point, but should not destroy the reference counters, because it will be destroyed by MakeNewRCObj
+ // Consider this example:
+ //
+ // A ==sp==> B ---wp---> A
+ //
+ // MakeNewRCObj::operator()
+ // try
+ // {
+ // A.ctor()
+ // B.ctor()
+ // wp.ctor m_lNumWeakReferences==1
+ // throw
+ // wp.dtor m_lNumWeakReferences==0, destroy this
+ // }
+ // catch(...)
+ // {
+ // Destory ref counters second time
+ // }
+ //
+ if( NumWeakReferences == 0 && /*m_lNumStrongReferences == 0 &&*/ m_ObjectState == ObjectState::Destroyed )
{
- m_pRefCounters = RefCountersImpl::Create(this);
+ VERIFY_EXPR(m_lNumStrongReferences == 0);
+ VERIFY( m_ObjectWrapperBuffer[0] == 0 && m_ObjectWrapperBuffer[1] == 0, "Object wrapper must be null")
+ // m_ObjectState is set to ObjectState::Destroyed under the lock. If the state is not Destroyed,
+ // ReleaseStrongRef() will take care of it.
+ // Access to Object wrapper and decrementing m_lNumWeakReferences is atomic. Since we acquired the lock,
+ // no other thread can access either of them.
+ // Access to m_lNumStrongReferences is NOT PROTECTED by lock.
+
+ // There are no more references to the ref counters object and the object itself
+ // is already destroyed.
+ // We can safely unlock it and destroy.
+ // If we do not unlock it, this->m_LockFlag will expire,
+ // which will cause Lock.~LockHelper() to crash.
+ Lock.Unlock();
+ SelfDestroy();
}
+ return NumWeakReferences;
+ }
+
+ inline virtual void GetObject( class IObject **ppObject )override final
+ {
+ if( m_ObjectState != ObjectState::Alive)
+ return; // Early exit
+
+ // It is essential to INCREMENT REF COUNTER while HOLDING THE LOCK to make sure that
+ // StrongRefCnt > 1 guarantees that the object is alive.
+
+ // If other thread started deleting the object in ReleaseStrongRef(), then m_lNumStrongReferences==0
+ // We must make sure only one thread is allowed to increment the counter to guarantee that if StrongRefCnt > 1,
+ // there is at least one real strong reference left. Otherwise the following scenario may occur:
+ //
+ // m_lNumStrongReferences == 1
+ //
+ // Thread 1 - ReleaseStrongRef() | Thread 2 - GetObject() | Thread 3 - GetObject()
+ // | |
+ // - Decrement m_lNumStrongReferences | -Increment m_lNumStrongReferences | -Increment m_lNumStrongReferences
+ // - Read RefCount == 0 | -Read StrongRefCnt==1 | -Read StrongRefCnt==2
+ // Destroy the object | | -Return reference to the soon
+ // | | to expire object
+ //
+ ThreadingTools::LockHelper Lock(m_LockFlag);
+
+ auto StrongRefCnt = Atomics::AtomicIncrement(m_lNumStrongReferences);
+
+ // Checking if m_ObjectState == ObjectState::Alive only is not reliable:
+ //
+ // This thread | Another thread
+ // |
+ // 1. Acquire the lock |
+ // | 1. Decrement m_lNumStrongReferences
+ // 2. Increment m_lNumStrongReferences | 2. Test RefCount==0
+ // 3. Read StrongRefCnt == 1 | 3. Start destroying the object
+ // m_ObjectState == ObjectState::Alive |
+ // 4. DO NOT return the reference to | 4. Wait for the lock, m_ObjectState == ObjectState::Alive
+ // the object |
+ // 5. Decrement m_lNumStrongReferences |
+ // | 5. Destroy the object
+
+ if( m_ObjectState == ObjectState::Alive && StrongRefCnt > 1 )
+ {
+ VERIFY( m_ObjectWrapperBuffer[0] != 0 && m_ObjectWrapperBuffer[1] != 0, "Object wrapper is not initialized")
+ // QueryInterface() must not lock the object, or a deadlock happens.
+ // The only other two methods that lock the object are ReleaseStrongRef()
+ // and ReleaseWeakRef(), which are never called by QueryInterface()
+ auto *pWrapper = reinterpret_cast<ObjectWrapperBase*>(m_ObjectWrapperBuffer);
+ pWrapper->QueryInterface(Diligent::IID_Unknown, ppObject);
+ }
+ Atomics::AtomicDecrement(m_lNumStrongReferences);
+ }
+
+ inline virtual Atomics::Long GetNumStrongRefs()const override final
+ {
+ return m_lNumStrongReferences;
+ }
+
+ inline virtual Atomics::Long GetNumWeakRefs()const override final
+ {
+ return m_lNumWeakReferences;
+ }
+
+private:
+ template<typename AllocatorType, typename ObjectType>
+ friend class MakeNewRCObj;
+
+ RefCountersImpl()noexcept
+ {
+ m_lNumStrongReferences = 0;
+ m_lNumWeakReferences = 0;
+#ifdef _DEBUG
+ memset(m_ObjectWrapperBuffer, 0, sizeof(m_ObjectWrapperBuffer));
+#endif
+ }
+
+ class ObjectWrapperBase
+ {
+ public:
+ virtual void DestroyObject() = 0;
+ virtual void QueryInterface( const Diligent::INTERFACE_ID &iid, IObject **ppInterface )=0;
};
+ template<typename ObjectType, typename AllocatorType>
+ class ObjectWrapper : public ObjectWrapperBase
+ {
+ public:
+ ObjectWrapper(ObjectType *pObject, AllocatorType *pAllocator)noexcept :
+ m_pObject(pObject),
+ m_pAllocator(pAllocator)
+ {}
+ virtual void DestroyObject()override final
+ {
+ if (m_pAllocator)
+ {
+ m_pObject->~ObjectType();
+ m_pAllocator->Free(m_pObject);
+ }
+ else
+ {
+ delete m_pObject;
+ }
+ }
+ virtual void QueryInterface( const Diligent::INTERFACE_ID &iid, IObject **ppInterface )override final
+ {
+ return m_pObject->QueryInterface(iid, ppInterface);
+ }
+ private:
+ // It is crucially important that the type of the pointer
+ // is ObjectType and not IObject, since the latter
+ // does not have virtual dtor.
+ ObjectType* const m_pObject;
+ AllocatorType * const m_pAllocator;
+ };
+
+ template<typename ObjectType, typename AllocatorType>
+ void Attach(ObjectType *pObject, AllocatorType *pAllocator)
+ {
+ VERIFY(m_ObjectState == ObjectState::NotInitialized, "Object has already been attached");
+ static_assert(sizeof(ObjectWrapper<ObjectType, AllocatorType>) == sizeof(m_ObjectWrapperBuffer), "Unexpected object wrapper size");
+ new(m_ObjectWrapperBuffer) ObjectWrapper<ObjectType, AllocatorType>(pObject, pAllocator);
+ m_ObjectState = ObjectState::Alive;
+ }
+
+ void SelfDestroy()
+ {
+ delete this;
+ }
+
+ ~RefCountersImpl()
+ {
+ VERIFY( m_lNumStrongReferences == 0 && m_lNumWeakReferences == 0,
+ "There exist outstanding references to the object being destroyed" );
+ }
+
+ // No copies/moves
+ RefCountersImpl(const RefCountersImpl&) = delete;
+ RefCountersImpl(RefCountersImpl&&) = delete;
+ RefCountersImpl& operator = (const RefCountersImpl&) = delete;
+ RefCountersImpl& operator = (RefCountersImpl&&) = delete;
+
+ static const size_t ObjectWrapperBufferSize = sizeof(ObjectWrapper<IObject, IMemoryAllocator>) / sizeof(size_t);
+ size_t m_ObjectWrapperBuffer[ObjectWrapperBufferSize];
+ Atomics::AtomicLong m_lNumStrongReferences;
+ Atomics::AtomicLong m_lNumWeakReferences;
+ ThreadingTools::LockFlag m_LockFlag;
+ enum class ObjectState : Int32
+ {
+ NotInitialized,
+ Alive,
+ Destroyed
+ };
+ volatile ObjectState m_ObjectState = ObjectState::NotInitialized;
+};
+
+
+/// Base class for all reference counting objects
+template<typename Base>
+class RefCountedObject : public Base
+{
+public:
+ RefCountedObject(IReferenceCounters *pRefCounters)noexcept :
+ m_pRefCounters( ValidatedCast<RefCountersImpl>(pRefCounters) )
+ {
+ // If object is allocated on stack, ref counters will be null
+ //VERIFY(pRefCounters != nullptr, "Reference counters must not be null")
+ }
+
+ // Virtual destructor makes sure all derived classes can be destroyed
+ // through the pointer to the base class
virtual ~RefCountedObject()
{
- // m_pRefCounters is set to null before executing delete this.
- //
- // WARNING! If m_pRefCounters was not set to null, it still may be expired in scenarios like this:
+ // WARNING! m_pRefCounters may be expired in scenarios like this:
//
// A ==sp==> B ---wp---> A
//
// RefCounters_A.ReleaseStrongRef(){ // NumStrongRef == 0, NumWeakRef == 1
- // RefCounters_A.m_pObject = nullptr;
// bDestroyThis = (m_lNumWeakReferences == 0) == false;
// delete A{
// A.~dtor(){
@@ -82,395 +500,143 @@ public:
//VERIFY( m_pRefCounters->GetNumStrongRefs() == 0,
// "There remain strong references to the object being destroyed" );
- };
+ }
- virtual IReferenceCounters* GetReferenceCounters()const override final
+ inline virtual IReferenceCounters* GetReferenceCounters()const override final
{
+ VERIFY_EXPR(m_pRefCounters != nullptr)
return m_pRefCounters;
}
- virtual Atomics::Long AddRef()override
+ inline virtual Atomics::Long AddRef()override final
{
+ VERIFY_EXPR(m_pRefCounters != nullptr)
+ // Since type of m_pRefCounters is RefCountersImpl,
+ // this call will not be virtual and should be inlined
return m_pRefCounters->AddStrongRef();
}
- virtual Atomics::Long Release()override
+ inline virtual Atomics::Long Release()override
{
+ VERIFY_EXPR(m_pRefCounters != nullptr)
+ // Since type of m_pRefCounters is RefCountersImpl,
+ // this call will not be virtual and should be inlined
return m_pRefCounters->ReleaseStrongRef();
}
- void* operator new(size_t Size)
- {
- return new Uint8[Size];
- }
+protected:
+ template<typename AllocatorType, typename ObjectType>
+ friend class MakeNewRCObj;
+
+ friend class RefCountersImpl;
+
+
+ // Operator delete can only be called from MakeNewRCObj if an exception is thrown,
+ // or from RefCountersImpl when object is destroyed
+ // It needs to be protected (not private!) to allow generation of destructors in derived classes
void operator delete(void *ptr)
{
delete[] reinterpret_cast<Uint8*>(ptr);
}
-
- void* operator new(size_t Size, TObjectAllocator &Allocator, const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber)
- {
- return Allocator.Allocate(Size, dbgDescription, dbgFileName, dbgLineNumber);
- }
-
- void operator delete(void *ptr, TObjectAllocator &Allocator, const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber)
+
+ template<typename ObjectAllocatorType>
+ void operator delete(void *ptr, ObjectAllocatorType &Allocator, const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber)
{
return Allocator.Free(ptr);
}
private:
+ // Operator new is private, and can only be called by MakeNewRCObj
- class RefCountersImpl : public IReferenceCounters
+ void* operator new(size_t Size)
{
- public:
- static RefCountersImpl* Create( RefCountedObject *pOwner )
- {
- return new RefCountersImpl( pOwner );
- }
-
- virtual Atomics::Long AddStrongRef()override final
- {
- VERIFY( m_pObject, "Attempting to increment strong reference counter for a destroyed object!" );
- return Atomics::AtomicIncrement(m_lNumStrongReferences);
- }
-
- virtual Atomics::Long ReleaseStrongRef()override final
- {
- // Decrement strong reference counter without acquiring the lock.
- auto RefCount = Atomics::AtomicDecrement(m_lNumStrongReferences);
- VERIFY( RefCount >= 0, "Inconsistent call to ReleaseStrongRef()" );
- if( RefCount == 0 )
- {
- // Since RefCount==0, there are no more strong references and the only place
- // where strong ref counter can be incremented is from GetObject().
-
- // There is a serious risk: if several threads get to this point,
- // then <this> may already be destroyed and m_LockFlag expired.
- // Consider the following scenario:
- // |
- // This thread | Another thread
- // |
- // m_lNumStrongReferences == 1
- // m_lNumWeakReferences == 1
- // |
- // 1. Decrement m_lNumStrongReferences |
- // Read RefCount==0, no lock acquired|
- // | 1. Run GetObject()
- // | - acquire the lock
- // | - increment m_lNumStrongReferences
- // | - release the lock
- // |
- // | 2. Run ReleaseWeakRef()
- // | - decrement m_lNumWeakReferences
- // |
- // | 3. Run ReleaseStrongRef()
- // | - decrement m_lNumStrongReferences
- // | - read RefCount==0
- //
- // Both threads will get to this point. The first one will destroy <this>
- // The second one will read expired m_LockFlag
-
- // IT IS CRUCIALLY IMPORTANT TO ASSURE THAT ONLY ONE THREAD WILL EVER
- // EXECUTE THIS CODE
-
- // The sloution is to atomically increment strong ref counter in GetObject().
- // There are two possible scenarios depending on who first increments the counter:
-
-
- // Scenario I
- //
- // This thread | Another thread - GetObject() | One more thread - GetObject()
- // | |
- // m_lNumStrongReferences == 1 |
- // | |
- // | 1. Acquire the lock |
- // 1. Decrement m_lNumStrongReferences | | 1. Wait for the lock
- // 2. Read RefCount==0 | 2. Increment m_lNumStrongReferences |
- // 3. Start destroying the object | 3. Read StrongRefCnt == 1 |
- // 4. Wait for the lock | 4. DO NOT return the reference |
- // | to the object |
- // | 5. Decrement m_lNumStrongReferences |
- // _ _ _ _ _ _ _ _ _ _ _ _ _| 6. Release the lock _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ _ _ _ _ _
- // | | 2. Acquire the lock
- // | | 3. Increment m_lNumStrongReferences
- // | | 4. Read StrongRefCnt == 1
- // | | 5. DO NOT return the reference
- // | | to the object
- // | | 6. Decrement m_lNumStrongReferences
- // _ _ _ _ _ _ _ _ _ _ _ _ | _ _ _ _ _ _ _ _ _ _ _ _ _ _ | _ 7. Release the lock _ _ _ _ _ _
- // 5. Acquire the lock | |
- // - m_lNumStrongReferences==0 | |
- // 6. DESTROY the object | |
- // | |
-
- // GetObject() MUST BE SERIALIZED for this to work properly!
-
-
- // Scenario II
- //
- // This thread | Another thread - GetObject()
- // |
- // m_lNumStrongReferences == 1
- // |
- // | 1. Acquire the lock
- // | 2. Increment m_lNumStrongReferences
- // 1. Decrement m_lNumStrongReferences |
- // 2. Read RefCount>0 |
- // 3. DO NOT destroy the object | 3. Read StrongRefCnt > 1 (while m_lNumStrongReferences == 1)
- // | 4. Return the reference to the object
- // | - Increment m_lNumStrongReferences
- // | 5. Decrement m_lNumStrongReferences
-
-#ifdef _DEBUG
- Atomics::Long NumStrongRefs = m_lNumStrongReferences;
- VERIFY( NumStrongRefs == 0 || NumStrongRefs == 1, "Num strong references (", NumStrongRefs, ") is expected to be 0 or 1" );
-#endif
-
- // Acquire the lock.
- ThreadingTools::LockHelper Lock(m_LockFlag);
-
- // GetObject() first acquires the lock, and only then increments and
- // decrements the ref counter. If it reads 1 after incremeting the counter,
- // it does not return the reference to the object and decrements the counter.
- // If we acquired the lock, GetObject() will not start until we are done
- VERIFY_EXPR( m_lNumStrongReferences == 0 && m_pObject != nullptr )
-
- // Extra caution
- if(m_lNumStrongReferences == 0 && m_pObject != nullptr)
- {
- // We cannot destroy the object while reference counters are locked as this will
- // cause a deadlock in cases like this:
- //
- // A ==sp==> B ---wp---> A
- //
- // RefCounters_A.Lock();
- // delete A{
- // A.~dtor(){
- // B.~dtor(){
- // wpA.ReleaseWeakRef(){
- // RefCounters_A.Lock(); // Deadlock
- //
-
- // So we store the pointer to the object and destory it after unlocking the
- // reference counters
- auto *pObj = m_pObject;
-
- // In a multithreaded environment, reference counters object may
- // be destroyed at any time while m_pObject->~dtor() is running.
- // NOTE: m_pObject may not be the only object referencing m_pRefCounters.
- // All objects that are owned by m_pObject will point to the same
- // reference counters object.
- m_pObject->m_pRefCounters = nullptr;
-
- // Note that this is the only place where m_pObject member is modified
- // after the ref counters object has been created
- m_pObject = nullptr;
- // The object is now detached from the reference counters and it is if
- // it was destroyed since no one can obtain access to it.
-
-
- // It is essentially important to check the number of weak references
- // while the object is locked. Otherwise reference counters object
- // may be destroyed twice if ReleaseWeakRef() is executed by other thread:
- //
- // This thread | Another thread - ReleaseWeakRef()
- // |
- // 1. Decrement m_lNumStrongReferences,|
- // m_lNumStrongReferences==0, |
- // acquire the lock, destroy |
- // the obj, release the lock |
- // m_lNumWeakReferences == 1 |
- // | 1. Aacquire the lock,
- // | decrement m_lNumWeakReferences,
- // | m_lNumWeakReferences == 0, m_pObject == nullptr
- // |
- // 2. Read m_lNumWeakReferences == 0 |
- // 3. Destroy the ref counters obj | 2. Destroy the ref counters obj
- //
- bool bDestroyThis = m_lNumWeakReferences == 0;
- // ReleaseWeakRef() decrements m_lNumWeakReferences, and checks it for
- // null only after acquiring the lock. So if m_lNumWeakReferences==0, no
- // weak reference-related code may be running
-
-
- // We must explicitly unlock the object now to avoid deadlocks. Also,
- // if this is deleted, this->m_LockFlag will expire, which will cause
- // Lock.~LockHelper() to crash
- Lock.Unlock();
-
- // Destroy referenced object
- //m_Delete(pObj);
- if (pObj->m_pAllocator)
- {
- auto *pAllocator = pObj->m_pAllocator;
- pObj->~RefCountedObject();
- pAllocator->Free(pObj);
- }
- else
- {
- delete pObj;
- }
-
- // Note that <this> may be destroyed here already,
- // see comments in ~RefCountedObject()
- if( bDestroyThis )
- delete this;
- }
- }
-
- return RefCount;
- }
-
- virtual Atomics::Long AddWeakRef()override final
- {
- return Atomics::AtomicIncrement(m_lNumWeakReferences);
- }
+ return new Uint8[Size];
+ }
- virtual Atomics::Long ReleaseWeakRef()override final
- {
- // All access to m_pObject must be atomic!
- ThreadingTools::LockHelper Lock(m_LockFlag);
- // It is essentially important to check the number of weak references
- // while the object is locked. Otherwise reference counters object
- // may be destroyed twice if ReleaseStrongRef() is executed by other
- // thread.
- auto NumWeakReferences = Atomics::AtomicDecrement(m_lNumWeakReferences);
- VERIFY( NumWeakReferences >= 0, "Inconsistent call to ReleaseWeakRef()" );
-
- // There is one special case when we must not destroy the ref counters object even
- // when NumWeakReferences == 0 && m_lNumStrongReferences == 0 :
- //
- // This thread | Another thread - ReleaseStrongRef()
- // |
- // 1. Lock the object |
- // |
- // 2. Decrement m_lNumWeakReferences, | 1. Decrement m_lNumStrongReferences,
- // m_lNumWeakReferences==0 | RefCount == 0
- // |
- // | 2. Start waiting for the lock to destroy
- // | the object, m_pObject != nullptr
- // 3. Do not destroy reference |
- // counters, unlock |
- // | 3. Acquire the lock,
- // | destroy the object,
- // | read m_lNumWeakReferences==0
- // | destroy the reference counters
- //
- if( NumWeakReferences == 0 && /*m_lNumStrongReferences == 0 &&*/ m_pObject == nullptr )
- {
- // m_pObject is set to null atomically. If it is not null, ReleaseStrongRef()
- // will take care of it.
- // Access to m_pObject and decrementing m_lNumWeakReferences is atomic. Since we acquired the lock,
- // no other thread can change either of them.
- // Access to m_lNumStrongReferences is NOT PROTECTED by lock.
-
- // There are no more references to the ref counters object and the object itself
- // is already destroyed.
- // We can safely unlock it and destroy.
- // If we do not unlock it, this->m_LockFlag will expire,
- // which will cause Lock.~LockHelper() to crash.
- Lock.Unlock();
- delete this;
- }
- return NumWeakReferences;
- }
+ template<typename ObjectAllocatorType>
+ void* operator new(size_t Size, ObjectAllocatorType &Allocator, const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber)
+ {
+ return Allocator.Allocate(Size, dbgDescription, dbgFileName, dbgLineNumber);
+ }
- virtual void GetObject( class IObject **ppObject )override final
- {
- if( m_pObject == nullptr)
- return; // Early exit
- // It is essential to INCREMENT REF COUNTER while object IS LOCKED to make sure that
- // StrongRefCnt > 1 guarantees that the object is alive.
+ // Note that the type of the reference counters is RefCountersImpl,
+ // not IReferenceCounters. This avoids virtual calls from
+ // AddRef() and Release() methods
+ RefCountersImpl *const m_pRefCounters;
+};
- // If other thread started deleting the object in ReleaseStrongRef(), then m_lNumStrongReferences==0
- // We must make sure only one thread is allowed to increment the counter to guarantee that if StrongRefCnt > 1,
- // there is at least one real strong reference left. Otherwise the following scenario may occur:
- //
- // m_lNumStrongReferences == 1
- //
- // Thread 1 - ReleaseStrongRef() | Thread 2 - GetObject() | Thread 3 - GetObject()
- // | |
- // - Decrement m_lNumStrongReferences | -Increment m_lNumStrongReferences | -Increment m_lNumStrongReferences
- // - Read RefCount == 0 | -Read StrongRefCnt==1 | -Read StrongRefCnt==2
- // Destroy the object | | -Return reference to the soon
- // | | to expire object
- //
- ThreadingTools::LockHelper Lock(m_LockFlag);
- auto StrongRefCnt = Atomics::AtomicIncrement(m_lNumStrongReferences);
-
- // Checking if m_pObject != nullptr is not reliable:
- //
- // This thread | Another thread -
- // |
- // 1. Acquire the lock |
- // | 1. Decrement m_lNumStrongReferences
- // 2. Increment m_lNumStrongReferences | 2. Test RefCount==0
- // 3. Read StrongRefCnt == 1 | 3. Start destroying the object
- // m_pObject != nullptr |
- // 4. DO NOT return the reference to | 4. Wait for the lock, m_pObject != nullptr
- // the object |
- // 5. Decrement m_lNumStrongReferences |
- // | 5. Destroy the object
-
- if( m_pObject && StrongRefCnt > 1 )
- {
- // QueryInterface() must not lock the object, or a deadlock happens.
- // The only other two methods that lock the object are ReleaseStrongRef()
- // and ReleaseWeakRef(), which are never called by QueryInterface()
- m_pObject->QueryInterface(Diligent::IID_Unknown, ppObject);
- }
- Atomics::AtomicDecrement(m_lNumStrongReferences);
- }
+template<typename ObjectType, typename AllocatorType = IMemoryAllocator>
+class MakeNewRCObj
+{
+public:
+ MakeNewRCObj(AllocatorType &Allocator, const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber, IObject* pOwner = nullptr)noexcept :
+ m_pAllocator(&Allocator),
+ m_pOwner(pOwner),
+ m_dbgDescription(dbgDescription),
+ m_dbgFileName(dbgFileName),
+ m_dbgLineNumber(dbgLineNumber)
+ {
+ }
- virtual Atomics::Long GetNumStrongRefs()const override final
- {
- return m_lNumStrongReferences;
- }
+ MakeNewRCObj(IObject* pOwner = nullptr)noexcept :
+ m_pAllocator(nullptr),
+ m_pOwner(pOwner),
+ m_dbgDescription(nullptr),
+ m_dbgFileName(nullptr),
+ m_dbgLineNumber(0)
+ {}
+
+ MakeNewRCObj(const MakeNewRCObj&) = delete;
+ MakeNewRCObj(MakeNewRCObj&&) = delete;
+ MakeNewRCObj& operator=(const MakeNewRCObj&) = delete;
+ MakeNewRCObj& operator=(MakeNewRCObj&&) = delete;
- virtual Atomics::Long GetNumWeakRefs()const override final
+ template<typename ... CtorArgTypes>
+ ObjectType* operator() (CtorArgTypes&& ... CtorArgs)
+ {
+ RefCountersImpl *pNewRefCounters = nullptr;
+ IReferenceCounters *pRefCounters = nullptr;
+ if(m_pOwner != nullptr)
+ pRefCounters = m_pOwner->GetReferenceCounters();
+ else
{
- return m_lNumWeakReferences;
+ // Constructor of RefCountersImpl class is private and only accessible
+ // by methods of MakeNewRCObj
+ pNewRefCounters = new RefCountersImpl();
+ pRefCounters = pNewRefCounters;
}
-
- private:
- RefCountersImpl(RefCountedObject *pOwner) :
- m_pObject(pOwner)
+ ObjectType *pObj = nullptr;
+ try
{
- VERIFY(m_pObject, "Owner must not be null");
- m_lNumStrongReferences = 0;
- m_lNumWeakReferences = 0;
+ // Operators new and delete of RefCountedObject are private and only accessible
+ // by methods of MakeNewRCObj
+ if(m_pAllocator)
+ pObj = new(*m_pAllocator, m_dbgDescription, m_dbgFileName, m_dbgLineNumber) ObjectType(pRefCounters, std::forward<CtorArgTypes>(CtorArgs)... );
+ else
+ pObj = new ObjectType( pRefCounters, std::forward<CtorArgTypes>(CtorArgs)... );
+ if(pNewRefCounters != nullptr)
+ pNewRefCounters->Attach<ObjectType, AllocatorType>(pObj, m_pAllocator);
}
-
- ~RefCountersImpl()
+ catch (...)
{
- VERIFY( m_lNumStrongReferences == 0 && m_lNumWeakReferences == 0,
- "There exist outstanding references to the object being destroyed" );
+ if(pNewRefCounters != nullptr)
+ pNewRefCounters->SelfDestroy();
+ throw;
}
-
- // No copies/moves
- RefCountersImpl(const RefCountersImpl&) = delete;
- RefCountersImpl(RefCountersImpl&&) = delete;
- RefCountersImpl& operator = (const RefCountersImpl&) = delete;
- RefCountersImpl& operator = (RefCountersImpl&&) = delete;
-
- // It is crucially important that the type of the pointer
- // is RefCountedObject and not IObject, since the latter
- // does not have virtual dtor.
- RefCountedObject *m_pObject;
- Atomics::AtomicLong m_lNumStrongReferences;
- Atomics::AtomicLong m_lNumWeakReferences;
- ThreadingTools::LockFlag m_LockFlag;
- };
+ return pObj;
+ }
- // Reference counters pointer cannot be of type RefCountersImpl*,
- // because when an owner pointer is provided to RefCountedObject(),
- // the type of pOwner->GetReferenceCounters() may not be convertible
- // to RefCountedObject<Base>::RefCountersImpl*.
- IReferenceCounters *m_pRefCounters;
- TObjectAllocator *m_pAllocator;
+private:
+ AllocatorType* const m_pAllocator;
+ IObject* const m_pOwner;
+ const Char* const m_dbgDescription;
+ const char* const m_dbgFileName;
+ const Int32 m_dbgLineNumber;
};
+#define NEW_RC_OBJ(Allocator, Desc, Type, ...) MakeNewRCObj<Type, typename std::remove_reference<decltype(Allocator)>::type>(Allocator, Desc, __FILE__, __LINE__, ##__VA_ARGS__)
+
}
diff --git a/Common/include/StringDataBlobImpl.h b/Common/include/StringDataBlobImpl.h
new file mode 100644
index 00000000..bb68722b
--- /dev/null
+++ b/Common/include/StringDataBlobImpl.h
@@ -0,0 +1,70 @@
+/* Copyright 2015-2017 Egor Yusov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS.
+ *
+ * In no event and under no legal theory, whether in tort (including negligence),
+ * contract, or otherwise, unless required by applicable law (such as deliberate
+ * and grossly negligent acts) or agreed to in writing, shall any Contributor be
+ * liable for any damages, including any direct, indirect, special, incidental,
+ * or consequential damages of any character arising as a result of this License or
+ * out of the use or inability to use the software (including but not limited to damages
+ * for loss of goodwill, work stoppage, computer failure or malfunction, or any and
+ * all other commercial damages or losses), even if such Contributor has been advised
+ * of the possibility of such damages.
+ */
+
+#pragma once
+
+/// \file
+/// Implementation for the IDataBlob interface
+
+#include "DataBlob.h"
+#include "BasicTypes.h"
+#include "ObjectBase.h"
+#include <vector>
+
+namespace Diligent
+{
+
+/// Base interface for a file stream
+class StringDataBlobImpl : public Diligent::ObjectBase<IDataBlob>
+{
+public:
+ typedef Diligent::ObjectBase<IDataBlob> TBase;
+
+ StringDataBlobImpl( IReferenceCounters* pRefCounters, const String &str ) : TBase(pRefCounters), m_String(str) {}
+ StringDataBlobImpl( IReferenceCounters* pRefCounters, String &&str ) : TBase(pRefCounters), m_String( std::move(str) ) {}
+
+ IMPLEMENT_QUERY_INTERFACE_IN_PLACE( IID_DataBlob, TBase )
+
+ /// Sets the size of the internal data buffer
+ virtual void Resize(size_t NewSize)override
+ {
+ m_String.resize(NewSize);
+ }
+
+ /// Returns the size of the internal data buffer
+ virtual size_t GetSize()override
+ {
+ return m_String.length();
+ }
+
+ /// Returns the pointer to the internal data buffer
+ virtual void* GetDataPtr()override
+ {
+ return &m_String[0];
+ }
+
+private:
+ String m_String;
+};
+
+}