1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_IMAGING_HD_INSTANCE_REGISTRY_H
25 #define PXR_IMAGING_HD_INSTANCE_REGISTRY_H
26
27 #include "pxr/pxr.h"
28 #include "pxr/imaging/hd/api.h"
29 #include "pxr/imaging/hd/version.h"
30 #include "pxr/imaging/hd/perfLog.h"
31 #include "pxr/imaging/hf/perfLog.h"
32
33 #include <tbb/concurrent_unordered_map.h>
34
35 #include <memory>
36 #include <mutex>
37
38 PXR_NAMESPACE_OPEN_SCOPE
39
40
41 /// \class HdInstance
42 ///
43 /// This class is used as an interface to a shared instance in
44 /// HdInstanceRegistry.
45 ///
46 /// KeyType is a hashable index type and VALUE is shared_ptr. In most use
47 /// cases, the client computes a hash key which represents large bulky data
48 /// (like topology, primvars) and registers it into HdInstanceRegistry. If the
49 /// key has already been registered, the registry returns HdInstance and the
50 /// client can use GetValue() without setting/computing actual bulky data. If
51 /// it doesn't exist, IsFirstInstance() returns true for the first instance
52 /// and the client needs to populate an appropriate data VALUE into the
53 /// instance by SetValue().
54 ///
55 /// In order to support concurrent access to HdInstanceRegistry, this
56 /// class holds a lock to a mutex in HdInstanceRegistry. This lock will
57 /// be held until the instance of this interface class is destroyed.
58 ///
59 template <typename VALUE>
60 class HdInstance {
61 public:
62 typedef uint64_t KeyType;
63 typedef VALUE ValueType;
64
65 typedef KeyType ID;
66
67 struct ValueHolder {
68 ValueHolder(ValueType const & value = ValueType())
valueValueHolder69 : value(value)
70 , recycleCounter(0)
71 { }
ResetRecycleCounterValueHolder72 void ResetRecycleCounter() {
73 recycleCounter = 0;
74 }
75
76 ValueType value;
77 int recycleCounter;
78 };
79 typedef tbb::concurrent_unordered_map<KeyType, ValueHolder> Dictionary;
80
81 typedef std::mutex RegistryMutex;
82 typedef std::unique_lock<RegistryMutex> RegistryLock;
83
84 HdInstance() = delete;
85
86 /// Construct an instance holding a registry lock, representing a value
87 /// held in a registry container.
HdInstance(KeyType const & key,ValueType const & value,RegistryLock && registryLock,Dictionary * container)88 explicit HdInstance(KeyType const &key,
89 ValueType const &value,
90 RegistryLock &®istryLock,
91 Dictionary *container)
92 : _key(key)
93 , _value(value)
94 , _registryLock(std::move(registryLock))
95 , _container(container)
96 , _isFirstInstance(!bool(_value))
97 { }
98
99 /// Construct an instance with no lock or registry container. This
100 /// is used to present a consistent interface to clients in cases
101 /// where shared resource registration is disabled.
HdInstance(KeyType const & key)102 explicit HdInstance(KeyType const &key)
103 : _key(key)
104 , _value(ValueType())
105 , _registryLock()
106 , _container(nullptr)
107 , _isFirstInstance(!bool(_value))
108 { }
109
110 /// Returns the key
GetKey()111 KeyType const &GetKey() const { return _key; }
112
113 /// Returns the value
GetValue()114 ValueType const &GetValue() const { return _value; }
115
116 /// Update the value in dictionary indexed by the key.
SetValue(ValueType const & value)117 void SetValue(ValueType const &value) {
118 if (_container) (*_container)[_key] = ValueHolder(value);
119 _value = value;
120 }
121
122 /// Returns true if the value has not been initialized.
IsFirstInstance()123 bool IsFirstInstance() const {
124 return _isFirstInstance;
125 }
126
127 private:
128 KeyType _key;
129 ValueType _value;
130 RegistryLock _registryLock;
131 Dictionary *_container;
132 bool _isFirstInstance;
133 };
134
135 /// \class HdInstanceRegistry
136 ///
137 /// HdInstanceRegistry is a dictionary container of HdInstance.
138 /// This class is almost just a dictionary from key to value.
139 /// For cleaning unused entries, it provides GarbageCollect() API.
140 /// It sweeps all entries in the dictionary and erase unreferenced entries.
141 /// When HdInstance::ValueType is shared_ptr, it is regarded as unreferenced
142 /// if the shared_ptr is unique (use_count==1). Note that Key is not
143 /// involved to determine the lifetime of entries.
144 ///
145 template <typename VALUE>
146 class HdInstanceRegistry {
147 public:
148 typedef HdInstance<VALUE> InstanceType;
149
150 HdInstanceRegistry() = default;
151
152 /// Copy constructor. Need as HdInstanceRegistry is placed in a map
153 /// and mutex is not copy constructable, so can't use default
HdInstanceRegistry(const HdInstanceRegistry & other)154 HdInstanceRegistry(const HdInstanceRegistry &other)
155 : _dictionary(other._dictionary)
156 , _registryMutex() // mutex is not copied
157 { }
158
159 /// Returns a shared instance for given key.
160 InstanceType GetInstance(
161 typename InstanceType::KeyType const &key);
162
163 /// Returns a shared instance for a given key
164 /// only if the key exists in the dictionary.
165 InstanceType FindInstance(
166 typename InstanceType::KeyType const &key, bool *found);
167
168 /// Removes unreferenced entries and returns the count
169 /// of remaining entries. When recycleCount is greater than zero,
170 /// unreferenced entries will not be removed until GarbageCollect() is
171 /// called that many more times, i.e. allowing unreferenced entries to
172 /// be recycled if they are needed again.
173 size_t GarbageCollect(int recycleCount = 0);
174
175 /// Returns a const iterator being/end of dictionary. Mainly used for
176 /// resource auditing.
177 typedef typename InstanceType::Dictionary::const_iterator const_iterator;
begin()178 const_iterator begin() const { return _dictionary.begin(); }
end()179 const_iterator end() const { return _dictionary.end(); }
180
size()181 size_t size() const { return _dictionary.size(); }
182
183 void Invalidate();
184
185 private:
186 template <typename T>
_IsUnique(std::shared_ptr<T> const & value)187 static bool _IsUnique(std::shared_ptr<T> const &value) {
188 return value.unique();
189 }
190
191 typename InstanceType::Dictionary _dictionary;
192 typename InstanceType::RegistryMutex _registryMutex;
193
194 HdInstanceRegistry &operator =(HdInstanceRegistry &) = delete;
195 };
196
197 // ---------------------------------------------------------------------------
198 // instance registry impl
199
200 template <typename VALUE>
201 HdInstance<VALUE>
GetInstance(typename HdInstance<VALUE>::KeyType const & key)202 HdInstanceRegistry<VALUE>::GetInstance(
203 typename HdInstance<VALUE>::KeyType const &key)
204 {
205 HD_TRACE_FUNCTION();
206 HF_MALLOC_TAG_FUNCTION();
207
208 // Grab Registry lock
209 // (and don't release it in this function, return it instead)
210 typename InstanceType::RegistryLock lock(_registryMutex);
211
212 typename InstanceType::Dictionary::iterator it = _dictionary.find(key);
213 if (it == _dictionary.end()) {
214 // not found. create new one
215 it = _dictionary.insert(
216 std::make_pair(key, typename InstanceType::ValueHolder())).first;
217 }
218
219 it->second.ResetRecycleCounter();
220 return InstanceType(key, it->second.value, std::move(lock), &_dictionary);
221 }
222
223 template <typename VALUE>
224 HdInstance<VALUE>
FindInstance(typename HdInstance<VALUE>::KeyType const & key,bool * found)225 HdInstanceRegistry<VALUE>::FindInstance(
226 typename HdInstance<VALUE>::KeyType const &key, bool *found)
227 {
228 HD_TRACE_FUNCTION();
229 HF_MALLOC_TAG_FUNCTION();
230
231 // Grab Registry lock
232 // (and don't release it in this function, return it instead)
233 typename InstanceType::RegistryLock lock(_registryMutex);
234
235 typename InstanceType::Dictionary::iterator it = _dictionary.find(key);
236 if (it == _dictionary.end()) {
237 *found = false;
238 return InstanceType(key, VALUE(), std::move(lock), nullptr);
239 } else {
240 *found = true;
241 it->second.ResetRecycleCounter();
242 return InstanceType(key, it->second.value,std::move(lock),&_dictionary);
243 }
244 }
245
246 template <typename VALUE>
247 size_t
GarbageCollect(int recycleCount)248 HdInstanceRegistry<VALUE>::GarbageCollect(int recycleCount)
249 {
250 HD_TRACE_FUNCTION();
251 HF_MALLOC_TAG_FUNCTION();
252
253 // Skip garbage collection entirely when then the recycleCount is < 0
254 if (recycleCount < 0) {
255 return _dictionary.size();
256 }
257
258 size_t inUseCount = 0;
259 for (typename InstanceType::Dictionary::iterator it = _dictionary.begin();
260 it != _dictionary.end();) {
261
262 // erase instance which isn't referred from anyone
263 bool isUnique = _IsUnique(it->second.value);
264 if (isUnique && (++it->second.recycleCounter > recycleCount)) {
265 it = _dictionary.unsafe_erase(it);
266 } else {
267 ++it;
268 ++inUseCount;
269 }
270 }
271 return inUseCount;
272 }
273
274 template <typename VALUE>
275 void
Invalidate()276 HdInstanceRegistry<VALUE>::Invalidate()
277 {
278 HD_TRACE_FUNCTION();
279 HF_MALLOC_TAG_FUNCTION();
280
281 _dictionary.clear();
282 }
283
284
285 PXR_NAMESPACE_CLOSE_SCOPE
286
287 #endif // PXR_IMAGING_HD_INSTANCE_REGISTRY_H
288