Diligent Engine API Reference
StateObjectsRegistry.h
1 /* Copyright 2015-2018 Egor Yusov
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS.
12  *
13  * In no event and under no legal theory, whether in tort (including negligence),
14  * contract, or otherwise, unless required by applicable law (such as deliberate
15  * and grossly negligent acts) or agreed to in writing, shall any Contributor be
16  * liable for any damages, including any direct, indirect, special, incidental,
17  * or consequential damages of any character arising as a result of this License or
18  * out of the use or inability to use the software (including but not limited to damages
19  * for loss of goodwill, work stoppage, computer failure or malfunction, or any and
20  * all other commercial damages or losses), even if such Contributor has been advised
21  * of the possibility of such damages.
22  */
23 
24 #pragma once
25 
28 
29 #include "DeviceObject.h"
30 #include <unordered_map>
31 #include "STDAllocator.h"
32 
33 namespace Diligent
34 {
36 
57  template<typename ResourceDescType>
59  {
60  public:
62  static constexpr int DeletedObjectsToPurge = 32;
63 
64  StateObjectsRegistry(IMemoryAllocator &RawAllocator, const Char* RegistryName) :
65  m_RegistryName( RegistryName ),
66  m_DescToObjHashMap(STD_ALLOCATOR_RAW_MEM(HashMapElem, RawAllocator, "Allocator for unordered_map<ResourceDescType, RefCntWeakPtr<IDeviceObject> >") )
67  {}
68 
70  {
71  // Object registry is part of the device, and every device
72  // object holds a strong reference to the device. So device
73  // is destroyed after all device objects are destroyed, and there
74  // may only be expired references in the registry. After we
75  // purge it, the registry must be empty.
76  Purge();
77  VERIFY( m_DescToObjHashMap.empty(), "DescToObjHashMap is not empty" );
78  }
79 
81 
91  void Add( const ResourceDescType& ObjectDesc, IDeviceObject *pObject )
92  {
93  ThreadingTools::LockHelper Lock( m_LockFlag );
94 
95  // If the number of outstanding deleted objects reached the threshold value,
96  // purge the registry. Since we have exclusive access now, it is safe
97  // to do.
98  if( m_NumDeletedObjects >= DeletedObjectsToPurge )
99  {
100  Purge();
101  m_NumDeletedObjects = 0;
102  }
103 
104  // Try to construct the new element in place
105  auto Elems = m_DescToObjHashMap.emplace( std::make_pair( ObjectDesc, Diligent::RefCntWeakPtr<IDeviceObject>(pObject) ) );
106  // It is theorertically possible that the same object can be found
107  // in the registry. This might happen if two threads try to create
108  // the same object at the same time. They both will not find the
109  // object and then will create and try to add it.
110  //
111  // If the object already exists, we replace the existing reference.
112  // This is safer as there might be scenarios where existing reference
113  // might be expired. For instance, two threads try to create the same
114  // object which is not in the registry. The first thread creates
115  // the object, adds it to the registry and then releases it. After that
116  // the second thread creates the same object and tries to add it to
117  // the registry. It will find an existing expired reference to the
118  // object.
119  if( !Elems.second )
120  {
121  VERIFY( Elems.first->first == ObjectDesc, "Incorrect object description" );
122  LOG_WARNING_MESSAGE( "Object named \"", Elems.first->first.Name, "\" with the same description already exists in the registry."
123  "Replacing with the new object named \"", ObjectDesc.Name ? ObjectDesc.Name : "", "\".");
124  Elems.first->second = pObject;
125  }
126  }
127 
129  void Find( const ResourceDescType &Desc, IDeviceObject **ppObject )
130  {
131  VERIFY( *ppObject == nullptr, "Overwriting reference to existing object may cause memory leaks" );
132  *ppObject = nullptr;
133  ThreadingTools::LockHelper Lock( m_LockFlag );
134 
135  auto It = m_DescToObjHashMap.find( Desc );
136  if( It != m_DescToObjHashMap.end() )
137  {
138  // Try to obtain strong reference to the object.
139  // This is an atomic operation and we either get
140  // a new strong reference or object has been destroyed
141  // and we get null.
142  auto pObject = It->second.Lock();
143  if( pObject )
144  {
145  *ppObject = pObject.Detach();
146  LOG_INFO_MESSAGE( "Equivalent of the requested state object named \"", Desc.Name ? Desc.Name : "", "\" found in the ", m_RegistryName, " registry. Reusing existing object.");
147  }
148  else
149  {
150  // Expired object found: remove it from the map
151  m_DescToObjHashMap.erase(It);
152  Atomics::AtomicDecrement(m_NumDeletedObjects);
153  }
154  }
155  }
156 
158  void Purge()
159  {
160  Uint32 NumPurgedObjects = 0;
161  auto It = m_DescToObjHashMap.begin();
162  while( It != m_DescToObjHashMap.end() )
163  {
164  auto NextIt = It;
165  ++NextIt;
166  // Note that IsValid() is not a thread-safe function in the sense that it
167  // can give false positive results. The only thread-safe way to check if the
168  // object is alive is to lock the weak pointer, but that requires thread
169  // synchronization. We will immediately unlock the pointer anyway, so we
170  // want to detect 100% expired pointers. IsValid() does provide that information
171  // because once a weak pointer becomes invalid, it will be invalid
172  // until it is destroyed. It is not a problem if we miss an expired weak
173  // pointer as it will definitiely be removed next time.
174  if( !It->second.IsValid() )
175  {
176  m_DescToObjHashMap.erase( It );
177  ++NumPurgedObjects;
178  }
179 
180  It = NextIt;
181  }
182  LOG_INFO_MESSAGE( "Purged ", NumPurgedObjects, " deleted objects from the ", m_RegistryName, " registry" );
183  }
184 
189  {
190  Atomics::AtomicIncrement(m_NumDeletedObjects);
191  }
192 
193  private:
195  ThreadingTools::LockFlag m_LockFlag;
196 
198  Atomics::AtomicLong m_NumDeletedObjects;
199 
201  typedef std::pair< const ResourceDescType, RefCntWeakPtr<IDeviceObject> > HashMapElem;
202  std::unordered_map<ResourceDescType, RefCntWeakPtr<IDeviceObject>, std::hash<ResourceDescType>, std::equal_to<ResourceDescType>, STDAllocatorRawMem<HashMapElem> > m_DescToObjHashMap;
203 
205  const String m_RegistryName;
206  };
207 }
Base interface for all objects created by the render device Diligent::IRenderDevice.
Definition: DeviceObject.h:40
static constexpr int DeletedObjectsToPurge
Number of outstanding deleted objects to purge the registry.
Definition: StateObjectsRegistry.h:62
Namespace for the OpenGL implementation of the graphics engine.
Definition: BufferD3D11Impl.h:34
Template class implementing state object registry.
Definition: StateObjectsRegistry.h:58
Implementation of weak pointers.
Definition: RefCntAutoPtr.h:38
void Find(const ResourceDescType &Desc, IDeviceObject **ppObject)
Finds the object in the registry.
Definition: StateObjectsRegistry.h:129
void Add(const ResourceDescType &ObjectDesc, IDeviceObject *pObject)
Adds a new object to the registry.
Definition: StateObjectsRegistry.h:91
void ReportDeletedObject()
Increments the number of outstanding deleted objects. When this number reaches DeletedObjectsToPurge...
Definition: StateObjectsRegistry.h:188
void Purge()
Purges outstanding deleted objects from the registry.
Definition: StateObjectsRegistry.h:158