Skip to content

Commit

Permalink
Added ObjectsRegistry template class
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMostDiligent committed Sep 1, 2023
1 parent 2ebd6cd commit 4ede319
Show file tree
Hide file tree
Showing 3 changed files with 510 additions and 0 deletions.
1 change: 1 addition & 0 deletions Common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(INTERFACE
interface/DynamicLinearAllocator.hpp
interface/MemoryFileStream.hpp
interface/ObjectBase.hpp
interface/ObjectsRegistry.hpp
interface/ParsingTools.hpp
interface/RefCntAutoPtr.hpp
interface/RefCountedObjectImpl.hpp
Expand Down
262 changes: 262 additions & 0 deletions Common/interface/ObjectsRegistry.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/* Copyright 2023 Diligent Graphics LLC
* 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

#include <unordered_map>
#include <mutex>
#include <memory>
#include <algorithm>

#include "../../../DiligentCore/Platforms/Basic/interface/DebugUtilities.hpp"
#include "RefCntAutoPtr.hpp"

namespace Diligent
{

template <typename StongPtrType>
struct _StrongPtrHelper;

// _StrongPtrHelper specialization for RefCntAutoPtr<T>
template <typename T>
struct _StrongPtrHelper<typename Diligent::RefCntAutoPtr<T>>
{
using WeakPtrType = RefCntWeakPtr<T>;
};

// _StrongPtrHelper specialization for std::shared_ptr<T>
template <typename T>
struct _StrongPtrHelper<std::shared_ptr<T>>
{
using WeakPtrType = std::weak_ptr<T>;
};

template <typename T>
auto _LockWeakPtr(RefCntWeakPtr<T>& pWeakPtr)
{
return pWeakPtr.Lock();
}

template <typename T>
auto _LockWeakPtr(std::weak_ptr<T>& pWeakPtr)
{
return pWeakPtr.lock();
}

/// A thread-safe and exception-safe object registry that works with std::shared_ptr or RefCntAutoPtr.
/// The registry keeps weak pointers to the objects and returns strong pointers if the requested object exits.
/// An application should keep strong pointers to the objects to keep them alive.
///
/// Usage example for RefCntAutoPtr:
///
/// ObjectsRegistry<std::string, RefCntAutoPtr<IObject>> Registry;
///
/// auto pObj = Registry.Get("Key",
/// []()
/// {
/// RefCntAutoPtr<IObject> pObj;
/// // Create object
/// return pObj;
/// });
///
/// Usage example for shared_ptr:
///
/// struct RegistryData
/// {
/// // ...
/// };
/// ObjectsRegistry<std::string, std::shared_ptr<RegistryData>> Registry;
///
/// auto pObj = Registry.Get("Key",
/// []()
/// {
/// return std::make_shared<RegistryData>();
/// });
///
///
/// \note If the object is not found in the registry, it is atomically created by the provided initializer function.
/// If the object is found, the initializer function is not called.
///
/// It is guaranteed, that the Object will only be initialized once, even if multiple threads call Get() simultaneously.
///
template <typename KeyType,
typename StrongPtrType,
typename KeyHasher = std::hash<KeyType>,
typename KeyEqual = std::equal_to<KeyType>>
class ObjectsRegistry
{
public:
using WeakPtrType = typename _StrongPtrHelper<StrongPtrType>::WeakPtrType;

ObjectsRegistry() noexcept
{}

/// Finds the object in the registry and returns strong pointer to it (std::shared_ptr or RefCntAutoPtr).
/// If the object is not found, it is atomically created using the provided initializer.
///
/// \param [in] Key - The object key.
/// \param [in] CreateObject - Initializer function that is called if the object is not found in the registry.
///
/// \return Strong pointer to the object with the specified key, either retrieved from the registry or initialized
/// with the CreateObject function.
///
/// \remarks CreateObject function may throw in case of an error.
///
/// It is guaranteed, that the Object will only be initialized once, even if multiple threads call Get() simultaneously.
/// However, if another thread runs an overloaded Get() without the initializer function with the same key, it may
/// remove the entry from the registry, and the object will be initialized multiple times.
/// This is OK as only one object will be added to the registry.
template <typename CreateObjectType>
StrongPtrType Get(const KeyType& Key,
CreateObjectType&& CreateObject // May throw
) noexcept(false)
{
// Get the Object wrapper. Since this is a shared pointer, it may not be destroyed
// while we keep one, even if it is popped from the registry by another thread.
std::shared_ptr<ObjectWrapper> pObjectWrpr;
{
std::lock_guard<std::mutex> Lock{m_CacheMtx};

auto it = m_Cache.find(Key);
if (it == m_Cache.end())
{
it = m_Cache.emplace(Key, std::make_shared<ObjectWrapper>()).first;
}
pObjectWrpr = it->second;
}

StrongPtrType pObject;
try
{
pObject = pObjectWrpr->Get(std::forward<CreateObjectType>(CreateObject));
}
catch (...)
{
std::lock_guard<std::mutex> Lock{m_CacheMtx};

auto it = m_Cache.find(Key);
if (it != m_Cache.end())
{
pObject = it->second->Lock();
if (pObject)
{
// The object was created by another thread while we were waiting for the lock
return pObject;
}
else
{
m_Cache.erase(it);
}
}

throw;
}

{
std::lock_guard<std::mutex> Lock{m_CacheMtx};

auto it = m_Cache.find(Key);
if (pObject)
{
if (it == m_Cache.end())
{
// The wrapper was removed from the cache by another thread while we were waiting
// for the lock - add it back.
m_Cache.emplace(Key, pObjectWrpr);
}
}
else
{
if (it != m_Cache.end())
{
pObject = it->second->Lock();
// Note that the object may have been created by another thread while we were waiting for the lock
if (!pObject)
m_Cache.erase(it);
}
}
}

return pObject;
}

/// Finds the object in the registry and returns a strong pointer to it (std::shared_ptr or RefCntAutoPtr).
/// If the object is not found, returns empty pointer.
///
/// \param [in] Key - The object key.
///
/// \return Strong pointer to the object with the specified key, if the object is found in the registry,
/// or empty pointer otherwise.
StrongPtrType Get(const KeyType& Key)
{
std::lock_guard<std::mutex> Lock{m_CacheMtx};

auto it = m_Cache.find(Key);
if (it != m_Cache.end())
{
auto pObject = it->second->Lock();
if (!pObject)
{
// Note that we may remove the entry from the cache while another thread is creating the object.
// This is OK as it will be added back to the cache.
m_Cache.erase(it);
}

return pObject;
}

return {};
}

private:
class ObjectWrapper
{
public:
template <typename CreateObjectType>
const StrongPtrType Get(CreateObjectType&& CreateObject) noexcept(false)
{
StrongPtrType pObject;

std::lock_guard<std::mutex> Lock{m_CreateObjectMtx};
pObject = _LockWeakPtr(m_wpObject);
if (!pObject)
{
pObject = CreateObject(); // May throw
m_wpObject = pObject;
}

return pObject;
}

StrongPtrType Lock()
{
return _LockWeakPtr(m_wpObject);
}

private:
std::mutex m_CreateObjectMtx;
WeakPtrType m_wpObject;
};

private:
using CacheType = std::unordered_map<KeyType, std::shared_ptr<ObjectWrapper>, KeyHasher, KeyEqual>;

std::mutex m_CacheMtx;
CacheType m_Cache;
};

} // namespace Diligent
Loading

0 comments on commit 4ede319

Please sign in to comment.