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 &&registryLock,
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