1 //
2 // Copyright 2018 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 
25 #include "pxr/pxr.h"
26 #include "pxr/base/tf/pathUtils.h"
27 #include "pxr/base/tf/stringUtils.h"
28 #include "pxr/base/tf/type.h"
29 #include "pxr/base/trace/trace.h"
30 #include "pxr/base/work/loops.h"
31 #include "pxr/base/work/withScopedParallelism.h"
32 #include "pxr/usd/ar/resolver.h"
33 #include "pxr/usd/ndr/debugCodes.h"
34 #include "pxr/usd/ndr/discoveryPlugin.h"
35 #include "pxr/usd/ndr/node.h"
36 #include "pxr/usd/ndr/nodeDiscoveryResult.h"
37 #include "pxr/usd/ndr/property.h"
38 #include "pxr/usd/ndr/registry.h"
39 #include "pxr/usd/sdf/types.h"
40 
41 #include "pxr/base/plug/registry.h"
42 #include "pxr/base/tf/envSetting.h"
43 
44 #include <boost/functional/hash.hpp>
45 
46 PXR_NAMESPACE_OPEN_SCOPE
47 
48 TF_DEFINE_ENV_SETTING(
49     PXR_NDR_SKIP_DISCOVERY_PLUGIN_DISCOVERY, 0,
50     "The auto-discovery of discovery plugins in ndr can be skipped. "
51     "This is used mostly for testing purposes.");
52 
53 TF_DEFINE_ENV_SETTING(
54     PXR_NDR_SKIP_PARSER_PLUGIN_DISCOVERY, 0,
55     "The auto-discovery of parser plugins in ndr can be skipped. "
56     "This is used mostly for testing purposes.");
57 
58 // This function is used for property validation. It is written as a non-static
59 // freestanding function so that we can exercise it in a test without needing
60 // to expose it in the header file.  It is also written without using unique
61 // pointers for ease of python wrapping and testability.
62 NDR_API
63 bool
NdrRegistry_ValidateProperty(const NdrNodeConstPtr & node,const NdrPropertyConstPtr & property,std::string * errorMessage)64 NdrRegistry_ValidateProperty(
65     const NdrNodeConstPtr& node,
66     const NdrPropertyConstPtr& property,
67     std::string* errorMessage)
68 {
69     const VtValue& defaultValue = property->GetDefaultValue();
70     const NdrSdfTypeIndicator sdfTypeIndicator = property->GetTypeAsSdfType();
71     const SdfValueTypeName sdfType = sdfTypeIndicator.first;
72 
73     // We allow default values to be unspecified, but if they aren't empty, then
74     // we want to error if the value's type is different from the specified type
75     // for the property.
76     if (!defaultValue.IsEmpty()) {
77         if (defaultValue.GetType() != sdfType.GetType()) {
78 
79             if (errorMessage) {
80                 *errorMessage = TfStringPrintf(
81                     "Default value type does not match specified type for "
82                     "property.\n"
83                     "Node identifier: %s\n"
84                     "Source type: %s\n"
85                     "Property name: %s.\n"
86                     "Type from SdfType: %s.\n"
87                     "Type from default value: %s.\n",
88                     node->GetIdentifier().GetString().c_str(),
89                     node->GetSourceType().GetString().c_str(),
90                     property->GetName().GetString().c_str(),
91                     sdfType.GetType().GetTypeName().c_str(),
92                     defaultValue.GetType().GetTypeName().c_str());
93             }
94 
95             return false;
96         }
97     }
98     return true;
99 }
100 
101 namespace {
102 
103 // Helpers to allow template functions to treat discovery results and
104 // nodes equally.
105 template <typename T> struct _NdrObjectAccess { };
106 template <> struct _NdrObjectAccess<NdrNodeDiscoveryResult> {
107     typedef NdrNodeDiscoveryResult Type;
GetName__anonc2f0b2fc0111::_NdrObjectAccess108     static const std::string& GetName(const Type& x) { return x.name; }
GetFamily__anonc2f0b2fc0111::_NdrObjectAccess109     static const TfToken& GetFamily(const Type& x) { return x.family; }
GetVersion__anonc2f0b2fc0111::_NdrObjectAccess110     static NdrVersion GetVersion(const Type& x) { return x.version; }
111 };
112 template <> struct _NdrObjectAccess<NdrNodeUniquePtr> {
113     typedef NdrNodeUniquePtr Type;
GetName__anonc2f0b2fc0111::_NdrObjectAccess114     static const std::string& GetName(const Type& x) { return x->GetName(); }
GetFamily__anonc2f0b2fc0111::_NdrObjectAccess115     static const TfToken& GetFamily(const Type& x) { return x->GetFamily(); }
GetVersion__anonc2f0b2fc0111::_NdrObjectAccess116     static NdrVersion GetVersion(const Type& x) { return x->GetVersion(); }
117 };
118 
119 template <typename T>
120 static
121 bool
_MatchesFamilyAndFilter(const T & object,const TfToken & family,NdrVersionFilter filter)122 _MatchesFamilyAndFilter(
123     const T& object,
124     const TfToken& family,
125     NdrVersionFilter filter)
126 {
127     using Access = _NdrObjectAccess<T>;
128 
129     // Check the family.
130     if (!family.IsEmpty() && family != Access::GetFamily(object)) {
131         return false;
132     }
133 
134     // Check the filter.
135     switch (filter) {
136     case NdrVersionFilterDefaultOnly:
137         if (!Access::GetVersion(object).IsDefault()) {
138             return false;
139         }
140         break;
141 
142     default:
143         break;
144     }
145 
146     return true;
147 }
148 
149 static NdrIdentifier
_GetIdentifierForAsset(const SdfAssetPath & asset,const NdrTokenMap & metadata,const TfToken & subIdentifier,const TfToken & sourceType)150 _GetIdentifierForAsset(const SdfAssetPath &asset,
151                        const NdrTokenMap &metadata,
152                        const TfToken &subIdentifier,
153                        const TfToken &sourceType)
154 {
155     size_t h = 0;
156     boost::hash_combine(h, asset);
157     for (const auto &i : metadata) {
158         boost::hash_combine(h, i.first.GetString());
159         boost::hash_combine(h, i.second);
160     }
161 
162     return NdrIdentifier(TfStringPrintf(
163         "%s<%s><%s>",
164         std::to_string(h).c_str(),
165         subIdentifier.GetText(),
166         sourceType.GetText()));
167 }
168 
169 static NdrIdentifier
_GetIdentifierForSourceCode(const std::string & sourceCode,const NdrTokenMap & metadata)170 _GetIdentifierForSourceCode(const std::string &sourceCode,
171                             const NdrTokenMap &metadata)
172 {
173     size_t h = 0;
174     boost::hash_combine(h, sourceCode);
175     for (const auto &i : metadata) {
176         boost::hash_combine(h, i.first.GetString());
177         boost::hash_combine(h, i.second);
178     }
179     return NdrIdentifier(std::to_string(h));
180 }
181 
182 static bool
_ValidateProperty(const NdrNodeConstPtr & node,const NdrPropertyConstPtr & property)183 _ValidateProperty(
184     const NdrNodeConstPtr& node,
185     const NdrPropertyConstPtr& property)
186 {
187     std::string errorMessage;
188     if (!NdrRegistry_ValidateProperty(node, property, &errorMessage)) {
189         // This warning may eventually want to be a runtime error and return
190         // false to indicate an invalid node, but we didn't want to introduce
191         // unexpected behaviors by introducing this error.
192         TF_WARN(errorMessage);
193     }
194     return true;
195 }
196 
197 static
198 bool
_ValidateNode(const NdrNodeUniquePtr & newNode,const NdrNodeDiscoveryResult & dr)199 _ValidateNode(const NdrNodeUniquePtr &newNode,
200               const NdrNodeDiscoveryResult &dr)
201 {
202     // Validate the node.
203     if (!newNode) {
204         TF_RUNTIME_ERROR("Parser for asset @%s@ of type %s returned null",
205             dr.resolvedUri.c_str(), dr.discoveryType.GetText());
206         return false;
207     }
208 
209     // The node is invalid; continue without further error checking.
210     //
211     // XXX -- WBN if these were just automatically copied and parser plugins
212     //        didn't have to deal with them.
213     if (newNode->IsValid() &&
214         !(newNode->GetIdentifier() == dr.identifier &&
215           newNode->GetName() == dr.name &&
216           newNode->GetVersion() == dr.version &&
217           newNode->GetFamily() == dr.family &&
218           newNode->GetSourceType() == dr.sourceType)) {
219         TF_RUNTIME_ERROR(
220                "Parsed node %s:%s:%s:%s:%s doesn't match discovery result "
221                "created for asset @%s@ - "
222                "%s:%s:%s:%s:%s (identifier:version:name:family:source type); "
223                "discarding.",
224                NdrGetIdentifierString(newNode->GetIdentifier()).c_str(),
225                newNode->GetVersion().GetString().c_str(),
226                newNode->GetName().c_str(),
227                newNode->GetFamily().GetText(),
228                newNode->GetSourceType().GetText(),
229                dr.resolvedUri.c_str(),
230                NdrGetIdentifierString(dr.identifier).c_str(),
231                dr.version.GetString().c_str(),
232                dr.name.c_str(),
233                dr.family.GetText(),
234                dr.sourceType.GetText());
235         return false;
236     }
237 
238     // It is safe to get the raw pointer from the unique pointer here since
239     // this raw pointer will not be passed beyond the scope of this function.
240     NdrNodeConstPtr node = newNode.get();
241 
242     // Validate the node's properties.  Always validate each property even if
243     // we have already found an invalid property because we want to report
244     // errors on all properties.
245     bool valid = true;
246     for (const TfToken& inputName : newNode->GetInputNames()) {
247         const NdrPropertyConstPtr& input = newNode->GetInput(inputName);
248         valid &= _ValidateProperty(node, input);
249     }
250 
251     for (const TfToken& outputName : newNode->GetOutputNames()) {
252         const NdrPropertyConstPtr& output = newNode->GetOutput(outputName);
253         valid &= _ValidateProperty(node, output);
254     }
255 
256     return valid;
257 }
258 
259 } // anonymous namespace
260 
261 class NdrRegistry::_DiscoveryContext : public NdrDiscoveryPluginContext {
262 public:
_DiscoveryContext(const NdrRegistry & registry)263     _DiscoveryContext(const NdrRegistry& registry) : _registry(registry) { }
264     ~_DiscoveryContext() override = default;
265 
GetSourceType(const TfToken & discoveryType) const266     TfToken GetSourceType(const TfToken& discoveryType) const override
267     {
268         auto parser = _registry._GetParserForDiscoveryType(discoveryType);
269         return parser ? parser->GetSourceType() : TfToken();
270     }
271 
272 private:
273     const NdrRegistry& _registry;
274 };
275 
NdrRegistry()276 NdrRegistry::NdrRegistry()
277 {
278     TRACE_FUNCTION();
279     _FindAndInstantiateParserPlugins();
280     _FindAndInstantiateDiscoveryPlugins();
281     _RunDiscoveryPlugins(_discoveryPlugins);
282 }
283 
~NdrRegistry()284 NdrRegistry::~NdrRegistry()
285 {
286     // nothing yet
287 }
288 
289 void
SetExtraDiscoveryPlugins(DiscoveryPluginRefPtrVec plugins)290 NdrRegistry::SetExtraDiscoveryPlugins(DiscoveryPluginRefPtrVec plugins)
291 {
292     {
293         std::lock_guard<std::mutex> nmLock(_nodeMapMutex);
294 
295         // This policy was implemented in order to keep internal registry
296         // operations simpler, and it "just makes sense" to have all plugins
297         // run before asking for information from the registry.
298         if (!_nodeMap.empty()) {
299             TF_CODING_ERROR("SetExtraDiscoveryPlugins() cannot be called after"
300                             " nodes have been parsed; ignoring.");
301             return;
302         }
303     }
304 
305     _RunDiscoveryPlugins(plugins);
306 
307     _discoveryPlugins.insert(_discoveryPlugins.end(),
308                              std::make_move_iterator(plugins.begin()),
309                              std::make_move_iterator(plugins.end()));
310 }
311 
312 void
SetExtraDiscoveryPlugins(const std::vector<TfType> & pluginTypes)313 NdrRegistry::SetExtraDiscoveryPlugins(const std::vector<TfType>& pluginTypes)
314 {
315     // Validate the types and remove duplicates.
316     std::set<TfType> discoveryPluginTypes;
317     auto& discoveryPluginType = TfType::Find<NdrDiscoveryPlugin>();
318     for (auto&& type: pluginTypes) {
319         if (!TF_VERIFY(type.IsA(discoveryPluginType),
320                        "Type %s is not a %s",
321                        type.GetTypeName().c_str(),
322                        discoveryPluginType.GetTypeName().c_str())) {
323             return;
324         }
325         discoveryPluginTypes.insert(type);
326     }
327 
328     // Instantiate any discovery plugins that were found
329     DiscoveryPluginRefPtrVec discoveryPlugins;
330     for (const TfType& discoveryPluginType : discoveryPluginTypes) {
331         NdrDiscoveryPluginFactoryBase* pluginFactory =
332             discoveryPluginType.GetFactory<NdrDiscoveryPluginFactoryBase>();
333 
334         if (TF_VERIFY(pluginFactory)) {
335             discoveryPlugins.emplace_back(pluginFactory->New());
336         }
337     }
338 
339     // Add the discovery plugins.
340     SetExtraDiscoveryPlugins(std::move(discoveryPlugins));
341 }
342 
343 void
SetExtraParserPlugins(const std::vector<TfType> & pluginTypes)344 NdrRegistry::SetExtraParserPlugins(const std::vector<TfType>& pluginTypes)
345 {
346     {
347         std::lock_guard<std::mutex> nmLock(_nodeMapMutex);
348 
349         // This policy was implemented in order to keep internal registry
350         // operations simpler, and it "just makes sense" to have all plugins
351         // run before asking for information from the registry.
352         if (!_nodeMap.empty()) {
353             TF_CODING_ERROR("SetExtraParserPlugins() cannot be called after"
354                             " nodes have been parsed; ignoring.");
355             return;
356         }
357     }
358 
359     // Validate the types and remove duplicates.
360     std::set<TfType> parserPluginTypes;
361     auto& parserPluginType = TfType::Find<NdrParserPlugin>();
362     for (auto&& type: pluginTypes) {
363         if (!TF_VERIFY(type.IsA(parserPluginType),
364                        "Type %s is not a %s",
365                        type.GetTypeName().c_str(),
366                        parserPluginType.GetTypeName().c_str())) {
367             return;
368         }
369         parserPluginTypes.insert(type);
370     }
371 
372     _InstantiateParserPlugins(parserPluginTypes);
373 }
374 
375 NdrNodeConstPtr
GetNodeFromAsset(const SdfAssetPath & asset,const NdrTokenMap & metadata,const TfToken & subIdentifier,const TfToken & sourceType)376 NdrRegistry::GetNodeFromAsset(const SdfAssetPath &asset,
377                               const NdrTokenMap &metadata,
378                               const TfToken &subIdentifier,
379                               const TfToken &sourceType)
380 {
381     // Ensure there is a parser plugin that can handle this asset.
382     TfToken discoveryType(ArGetResolver().GetExtension(asset.GetAssetPath()));
383     auto parserIt = _parserPluginMap.find(discoveryType);
384 
385     // Ensure that there is a parser registered corresponding to the
386     // discoveryType of the asset.
387     if (parserIt == _parserPluginMap.end()) {
388         TF_DEBUG(NDR_PARSING).Msg("Encountered a asset @%s@ of type [%s], but "
389                                   "a parser for the type could not be found; "
390                                   "ignoring.\n", asset.GetAssetPath().c_str(),
391                                   discoveryType.GetText());
392         return nullptr;
393     }
394 
395     NdrIdentifier identifier =
396         _GetIdentifierForAsset(asset, metadata, subIdentifier, sourceType);
397 
398     // Use given sourceType if there is one, else use sourceType from the parser
399     // plugin.
400     const TfToken &thisSourceType = (!sourceType.IsEmpty()) ? sourceType :
401         parserIt->second->GetSourceType();
402     NodeMapKey key{identifier, thisSourceType};
403 
404     // Return the existing node in the map if an entry for the constructed node
405     // key already exists.
406     std::unique_lock<std::mutex> nmLock(_nodeMapMutex);
407     auto it = _nodeMap.find(key);
408     if (it != _nodeMap.end()) {
409         // Get the raw ptr from the unique_ptr
410         return it->second.get();
411     }
412 
413     // Ensure the map is not locked at this point. The parse is the bulk of the
414     // operation, and concurrency is the most valuable here.
415     nmLock.unlock();
416 
417     // Construct a NdrNodeDiscoveryResult object to pass into the parser
418     // plugin's Parse() method.
419     // XXX: Should we try resolving the assetPath if the resolved path is empty.
420     std::string resolvedUri = asset.GetResolvedPath().empty() ?
421         asset.GetAssetPath() : asset.GetResolvedPath();
422 
423     NdrNodeDiscoveryResult dr(identifier,
424                               NdrVersion(), /* use an invalid version */
425                               /* name */ TfGetBaseName(resolvedUri),
426                               /*family*/ TfToken(),
427                               discoveryType,
428                               /* sourceType */ thisSourceType,
429                               /* uri */ asset.GetAssetPath(),
430                               resolvedUri,
431                               /* sourceCode */ "",
432                               metadata,
433                               /* blindData */ "",
434                               /* subIdentifier */ subIdentifier);
435 
436     NdrNodeUniquePtr newNode = parserIt->second->Parse(dr);
437 
438     if (!_ValidateNode(newNode, dr)) {
439         return nullptr;
440     }
441 
442     // Move the discovery result into _discoveryResults so the node can be found
443     // in the Get*() methods
444     {
445         std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
446         _discoveryResultIndicesBySourceType[dr.sourceType].push_back(
447             _discoveryResults.size());
448         _discoveryResults.emplace_back(std::move(dr));
449     }
450 
451     nmLock.lock();
452 
453     NodeMap::const_iterator result =
454         _nodeMap.emplace(std::move(key), std::move(newNode));
455 
456     // Get the unique_ptr from the iterator, then get its raw ptr
457     return result->second.get();
458 }
459 
460 NdrNodeConstPtr
GetNodeFromSourceCode(const std::string & sourceCode,const TfToken & sourceType,const NdrTokenMap & metadata)461 NdrRegistry::GetNodeFromSourceCode(const std::string &sourceCode,
462                                    const TfToken &sourceType,
463                                    const NdrTokenMap &metadata)
464 {
465     // Ensure that there is a parser registered corresponding to the
466     // given sourceType.
467     NdrParserPlugin *parserForSourceType = nullptr;
468     for (const auto &parserIt : _parserPlugins) {
469         if (parserIt->GetSourceType() == sourceType) {
470             parserForSourceType = parserIt.get();
471         }
472     }
473 
474     if (!parserForSourceType) {
475         // XXX: Should we try looking for sourceType in _parserPluginMap,
476         // in case it corresponds to a discovery type in Ndr?
477 
478         TF_DEBUG(NDR_PARSING).Msg("Encountered source code of type [%s], but "
479                                   "a parser for the type could not be found; "
480                                   "ignoring.\n", sourceType.GetText());
481         return nullptr;
482     }
483 
484     NdrIdentifier identifier = _GetIdentifierForSourceCode(sourceCode,
485             metadata);
486     NodeMapKey key{identifier, sourceType};
487 
488     // Return the existing node in the map if an entry for the constructed node
489     // key already exists.
490     std::unique_lock<std::mutex> nmLock(_nodeMapMutex);
491     auto it = _nodeMap.find(key);
492     if (it != _nodeMap.end()) {
493         // Get the raw ptr from the unique_ptr
494         return it->second.get();
495     }
496 
497     // Ensure the map is not locked at this point. The parse is the bulk of the
498     // operation, and concurrency is the most valuable here.
499     nmLock.unlock();
500 
501     NdrNodeDiscoveryResult dr(identifier,
502                               NdrVersion(), /* use an invalid version */
503                               /* name */ identifier,
504                               /*family*/ TfToken(),
505                               // XXX: Setting discoveryType also to sourceType.
506                               // Do ParserPlugins rely on it? If yes, should they?
507                               /* discoveryType */ sourceType,
508                               sourceType,
509                               /* uri */ "",
510                               /* resolvedUri */ "",
511                                sourceCode,
512                                metadata);
513 
514     NdrNodeUniquePtr newNode = parserForSourceType->Parse(dr);
515     if (!newNode) {
516         TF_RUNTIME_ERROR("Could not create node for the given source code of "
517             "source type '%s'.", sourceType.GetText());
518         return nullptr;
519     }
520 
521     // Move the discovery result into _discoveryResults so the node can be found
522     // in the Get*() methods
523     {
524         std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
525         _discoveryResultIndicesBySourceType[dr.sourceType].push_back(
526             _discoveryResults.size());
527         _discoveryResults.emplace_back(std::move(dr));
528     }
529 
530     nmLock.lock();
531 
532     NodeMap::const_iterator result =
533         _nodeMap.emplace(std::move(key), std::move(newNode));
534 
535     // Get the unique_ptr from the iterator, then get its raw ptr
536     return result->second.get();
537 }
538 
539 NdrStringVec
GetSearchURIs() const540 NdrRegistry::GetSearchURIs() const
541 {
542     NdrStringVec searchURIs;
543 
544     for (const NdrDiscoveryPluginRefPtr& dp : _discoveryPlugins) {
545         NdrStringVec uris = dp->GetSearchURIs();
546 
547         searchURIs.insert(searchURIs.end(),
548                           std::make_move_iterator(uris.begin()),
549                           std::make_move_iterator(uris.end()));
550     }
551 
552     return searchURIs;
553 }
554 
555 NdrIdentifierVec
GetNodeIdentifiers(const TfToken & family,NdrVersionFilter filter) const556 NdrRegistry::GetNodeIdentifiers(
557     const TfToken& family, NdrVersionFilter filter) const
558 {
559     //
560     // This should not trigger a parse because node names come directly from
561     // the discovery process.
562     //
563 
564     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
565 
566     NdrIdentifierVec result;
567     result.reserve(_discoveryResults.size());
568 
569     NdrIdentifierSet visited;
570     for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
571         if (_MatchesFamilyAndFilter(dr, family, filter)) {
572             // Avoid duplicates.
573             if (visited.insert(dr.identifier).second) {
574                 result.push_back(dr.identifier);
575             }
576         }
577     }
578 
579     return result;
580 }
581 
582 NdrStringVec
GetNodeNames(const TfToken & family) const583 NdrRegistry::GetNodeNames(const TfToken& family) const
584 {
585     //
586     // This should not trigger a parse because node names come directly from
587     // the discovery process.
588     //
589 
590     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
591 
592     NdrStringVec nodeNames;
593     nodeNames.reserve(_discoveryResults.size());
594 
595     NdrStringSet visited;
596     for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
597         if (family.IsEmpty() || dr.family == family) {
598             // Avoid duplicates.
599             if (visited.insert(dr.name).second) {
600                 nodeNames.push_back(dr.name);
601             }
602         }
603     }
604 
605     return nodeNames;
606 }
607 
608 NdrNodeConstPtr
GetNodeByIdentifier(const NdrIdentifier & identifier,const NdrTokenVec & sourceTypePriority)609 NdrRegistry::GetNodeByIdentifier(
610     const NdrIdentifier& identifier, const NdrTokenVec& sourceTypePriority)
611 {
612     TRACE_FUNCTION();
613     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
614 
615     // If the type priority specifier is empty, pick the first node that matches
616     // the identifier regardless of source type.
617     if (sourceTypePriority.empty()) {
618         // We check for any node that matches the identifier first. If no
619         // matching node is found we'll try to find a node with an alias
620         // matching the identifier.
621         for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
622             if (NdrNodeConstPtr node =
623                     _ParseNodeMatchingIdentifier(dr, identifier)) {
624                 return node;
625             }
626         }
627 
628         for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
629             if (NdrNodeConstPtr node =
630                     _ParseNodeMatchingAlias(dr, identifier)) {
631                 return node;
632             }
633         }
634     }
635 
636     // Otherwise we attempt to get a node for matching the identifier (possibly
637     // through an alias) for each source type in priority order.
638     for (const TfToken& sourceType : sourceTypePriority) {
639         if (NdrNodeConstPtr node =
640                 _GetNodeByIdentifierAndTypeImpl(identifier, sourceType)) {
641             return node;
642         }
643     }
644 
645     return nullptr;
646 }
647 
648 NdrNodeConstPtr
GetNodeByIdentifierAndType(const NdrIdentifier & identifier,const TfToken & sourceType)649 NdrRegistry::GetNodeByIdentifierAndType(
650     const NdrIdentifier& identifier, const TfToken& sourceType)
651 {
652     TRACE_FUNCTION();
653     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
654 
655     return _GetNodeByIdentifierAndTypeImpl(identifier, sourceType);
656 }
657 
658 NdrNodeConstPtr
_GetNodeByIdentifierAndTypeImpl(const NdrIdentifier & identifier,const TfToken & sourceType)659 NdrRegistry::_GetNodeByIdentifierAndTypeImpl(
660     const NdrIdentifier& identifier, const TfToken& sourceType)
661 {
662     // Note that this impl function doesn't lock the discovery result mutex as
663     // it expects the caller to have appropriately locked it.
664 
665     const auto it = _discoveryResultIndicesBySourceType.find(sourceType);
666     if (it == _discoveryResultIndicesBySourceType.end()) {
667         return nullptr;
668     }
669 
670     const std::vector<size_t> &indices = it->second;
671 
672     // We check for any node that matches the identifier first. If no
673     // matching node is found we'll try to find a node with an alias
674     // matching the identifier.
675     for (size_t index : indices) {
676         const NdrNodeDiscoveryResult &dr = _discoveryResults[index];
677         if (NdrNodeConstPtr node = _ParseNodeMatchingIdentifier(dr, identifier)) {
678             return node;
679         }
680     }
681     for (size_t index : indices) {
682         const NdrNodeDiscoveryResult &dr = _discoveryResults[index];
683         if (NdrNodeConstPtr node = _ParseNodeMatchingAlias(dr, identifier)) {
684             return node;
685         }
686     }
687 
688     return nullptr;
689 }
690 
691 NdrNodeConstPtr
GetNodeByName(const std::string & name,const NdrTokenVec & sourceTypePriority,NdrVersionFilter filter)692 NdrRegistry::GetNodeByName(
693     const std::string& name,
694     const NdrTokenVec& sourceTypePriority,
695     NdrVersionFilter filter)
696 {
697     TRACE_FUNCTION();
698     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
699 
700     // If the type priority specifier is empty, pick the first node that matches
701     // the name
702     if (sourceTypePriority.empty()) {
703         for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
704             if (NdrNodeConstPtr node =
705                     _ParseNodeMatchingNameAndFilter(dr, name, filter)) {
706                 return node;
707             }
708         }
709     }
710 
711     // Although this is a doubly-nested loop, the number of types in the
712     // priority list should be small as should the number of nodes.
713     for (const TfToken& sourceType : sourceTypePriority) {
714         if (NdrNodeConstPtr node =
715                 _GetNodeByNameAndTypeImpl(name, sourceType, filter)) {
716             return node;
717         }
718     }
719 
720     return nullptr;
721 }
722 
723 NdrNodeConstPtr
GetNodeByNameAndType(const std::string & name,const TfToken & sourceType,NdrVersionFilter filter)724 NdrRegistry::GetNodeByNameAndType(
725     const std::string& name, const TfToken& sourceType, NdrVersionFilter filter)
726 {
727     TRACE_FUNCTION();
728     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
729 
730     return _GetNodeByNameAndTypeImpl(name, sourceType, filter);
731 }
732 
733 NdrNodeConstPtr
_GetNodeByNameAndTypeImpl(const std::string & name,const TfToken & sourceType,NdrVersionFilter filter)734 NdrRegistry::_GetNodeByNameAndTypeImpl(
735     const std::string& name, const TfToken& sourceType,
736     NdrVersionFilter filter)
737 {
738     // Note that this impl function doesn't lock the discovery result mutex as
739     // it expects the caller to have appropriately locked it.
740 
741     const auto it = _discoveryResultIndicesBySourceType.find(sourceType);
742     if (it == _discoveryResultIndicesBySourceType.end()) {
743         return nullptr;
744     }
745 
746     const std::vector<size_t> &indices = it->second;
747 
748     for (size_t index : indices) {
749         const NdrNodeDiscoveryResult &dr = _discoveryResults[index];
750         if (NdrNodeConstPtr node =
751                 _ParseNodeMatchingNameAndFilter(dr, name, filter)) {
752             return node;
753         }
754     }
755     return nullptr;
756 
757 }
758 
759 NdrNodeConstPtrVec
GetNodesByIdentifier(const NdrIdentifier & identifier)760 NdrRegistry::GetNodesByIdentifier(const NdrIdentifier& identifier)
761 {
762     TRACE_FUNCTION();
763     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
764     NdrNodeConstPtrVec parsedNodes;
765 
766     for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
767         if (NdrNodeConstPtr node =
768                 _ParseNodeMatchingIdentifier(dr, identifier)) {
769             parsedNodes.push_back(node);
770         }
771     }
772 
773     for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
774         if (NdrNodeConstPtr node = _ParseNodeMatchingAlias(dr, identifier)) {
775             // In the extremely unlikely case that a node is supplied with an
776             // alias that is the same as its identifier, we just make sure it
777             // doesn't show up in the list twice as it will have already been
778             // added by the above loop.
779             if (ARCH_LIKELY(dr.identifier != identifier)) {
780                 parsedNodes.push_back(node);
781             }
782         }
783     }
784 
785     return parsedNodes;
786 }
787 
788 NdrNodeConstPtrVec
GetNodesByName(const std::string & name,NdrVersionFilter filter)789 NdrRegistry::GetNodesByName(const std::string& name, NdrVersionFilter filter)
790 {
791     TRACE_FUNCTION();
792     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
793     NdrNodeConstPtrVec parsedNodes;
794 
795     for (const NdrNodeDiscoveryResult& dr : _discoveryResults) {
796         if (NdrNodeConstPtr node = _ParseNodeMatchingNameAndFilter(
797                 dr, name, filter)) {
798             parsedNodes.push_back(node);
799         }
800     }
801 
802     return parsedNodes;
803 }
804 
805 NdrNodeConstPtrVec
GetNodesByFamily(const TfToken & family,NdrVersionFilter filter)806 NdrRegistry::GetNodesByFamily(const TfToken& family, NdrVersionFilter filter)
807 {
808     // Locking the discovery results for the entire duration of the parse is a
809     // bit heavy-handed, but it needs to be 100% guaranteed that the results are
810     // not modified while they are being iterated over.
811     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
812 
813     // This method does a multi-threaded "bulk parse" of all discovered nodes
814     // (or a partial parse if a family is specified). It's possible that another
815     // node access method (potentially triggering a parse) could be called in
816     // another thread during bulk parse. In that scenario, the worst that should
817     // happen is that one of the parses (either from the other method, or this
818     // bulk parse) is discarded in favor of the other parse result
819     // (_InsertNodeIntoCache() will guard against nodes of the same name and
820     // type from being cached).
821     {
822         std::lock_guard<std::mutex> nmLock(_nodeMapMutex);
823 
824         // Skip parsing if a parse was already completed for all nodes
825         if (_nodeMap.size() == _discoveryResults.size()) {
826             return _GetNodeMapAsNodePtrVec(family, filter);
827         }
828     }
829 
830     // Do the parsing. We need to release the Python GIL here to avoid
831     // deadlocks since the code running in the worker threads may call into
832     // Python and try to take the GIL when loading plugins. We also need
833     // to use scoped parallelism to ensure we don't pick up other tasks
834     // during the call to WorkParallelForN that may reenter this function
835     // and also deadlock.
836     {
837         TF_PY_ALLOW_THREADS_IN_SCOPE();
838 
839         WorkWithScopedParallelism([&]() {
840             WorkParallelForN(_discoveryResults.size(),
841                 [&](size_t begin, size_t end) {
842                     for (size_t i = begin; i < end; ++i) {
843                         const NdrNodeDiscoveryResult& dr = _discoveryResults.at(i);
844                         if (_MatchesFamilyAndFilter(dr, family, filter)) {
845                             _InsertNodeIntoCache(dr);
846                         }
847                     }
848                 });
849             }
850         );
851     }
852     // Expose the concurrent map as a normal vector to the outside world
853     return _GetNodeMapAsNodePtrVec(family, filter);
854 }
855 
856 NdrTokenVec
GetAllNodeSourceTypes() const857 NdrRegistry::GetAllNodeSourceTypes() const
858 {
859     // We're using the _discoveryResultMutex because we populate the
860     // _availableSourceTypes while creating the _discoveryResults.
861     //
862     // We also have to return the source types by value instead of by const
863     // reference because we don't want a client holding onto the reference
864     // to read from it when _RunDiscoveryPlugins could potentially be running
865     // and modifying _availableSourceTypes
866     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
867     NdrTokenVec availableSourceTypes;
868     availableSourceTypes.reserve(_discoveryResultIndicesBySourceType.size());
869     for (const auto &valuePair : _discoveryResultIndicesBySourceType) {
870         availableSourceTypes.push_back(valuePair.first);
871     }
872     return availableSourceTypes;
873 }
874 
875 NdrNodeConstPtr
_ParseNodeMatchingIdentifier(const NdrNodeDiscoveryResult & dr,const NdrIdentifier & identifier)876 NdrRegistry::_ParseNodeMatchingIdentifier(
877     const NdrNodeDiscoveryResult& dr, const NdrIdentifier& identifier)
878 {
879     if (dr.identifier == identifier) {
880         return _InsertNodeIntoCache(dr);
881     }
882     return nullptr;
883 }
884 
885 NdrNodeConstPtr
_ParseNodeMatchingAlias(const NdrNodeDiscoveryResult & dr,const NdrIdentifier & identifier)886 NdrRegistry::_ParseNodeMatchingAlias(
887     const NdrNodeDiscoveryResult& dr, const NdrIdentifier& identifier)
888 {
889     for (const TfToken &alias : dr.aliases) {
890         if (alias == identifier) {
891             return _InsertNodeIntoCache(dr);
892         }
893     }
894     return nullptr;
895 }
896 
897 NdrNodeConstPtr
_ParseNodeMatchingNameAndFilter(const NdrNodeDiscoveryResult & dr,const std::string & name,NdrVersionFilter filter)898 NdrRegistry::_ParseNodeMatchingNameAndFilter(
899     const NdrNodeDiscoveryResult& dr, const std::string& name,
900     NdrVersionFilter filter)
901 {
902     // Check the filter.
903     if (filter == NdrVersionFilterDefaultOnly && !dr.version.IsDefault()) {
904         return nullptr;
905     }
906 
907     if (dr.name == name) {
908         return _InsertNodeIntoCache(dr);
909     }
910     return nullptr;
911 }
912 
913 void
_FindAndInstantiateDiscoveryPlugins()914 NdrRegistry::_FindAndInstantiateDiscoveryPlugins()
915 {
916     // The auto-discovery of discovery plugins can be skipped. This is mostly
917     // for testing purposes.
918     if (TfGetEnvSetting(PXR_NDR_SKIP_DISCOVERY_PLUGIN_DISCOVERY)) {
919         return;
920     }
921 
922     // Find all of the available discovery plugins
923     std::set<TfType> discoveryPluginTypes;
924     PlugRegistry::GetInstance().GetAllDerivedTypes<NdrDiscoveryPlugin>(
925         &discoveryPluginTypes);
926 
927     // Instantiate any discovery plugins that were found
928     for (const TfType& discoveryPluginType : discoveryPluginTypes) {
929         TF_DEBUG(NDR_DISCOVERY).Msg(
930             "Found NdrDiscoveryPlugin '%s'\n",
931             discoveryPluginType.GetTypeName().c_str());
932 
933         NdrDiscoveryPluginFactoryBase* pluginFactory =
934             discoveryPluginType.GetFactory<NdrDiscoveryPluginFactoryBase>();
935 
936         if (TF_VERIFY(pluginFactory)) {
937             _discoveryPlugins.emplace_back(pluginFactory->New());
938         }
939     }
940 }
941 
942 void
_FindAndInstantiateParserPlugins()943 NdrRegistry::_FindAndInstantiateParserPlugins()
944 {
945     // The auto-discovery of parser plugins can be skipped. This is mostly
946     // for testing purposes.
947     if (TfGetEnvSetting(PXR_NDR_SKIP_PARSER_PLUGIN_DISCOVERY)) {
948         return;
949     }
950 
951     // Find all of the available parser plugins
952     std::set<TfType> parserPluginTypes;
953     PlugRegistry::GetInstance().GetAllDerivedTypes<NdrParserPlugin>(
954         &parserPluginTypes);
955 
956     _InstantiateParserPlugins(parserPluginTypes);
957 }
958 
959 void
_InstantiateParserPlugins(const std::set<TfType> & parserPluginTypes)960 NdrRegistry::_InstantiateParserPlugins(
961     const std::set<TfType>& parserPluginTypes)
962 {
963     // Instantiate any parser plugins that were found
964     for (const TfType& parserPluginType : parserPluginTypes) {
965         TF_DEBUG(NDR_DISCOVERY).Msg(
966             "Found NdrParserPlugin '%s' for discovery types:\n",
967             parserPluginType.GetTypeName().c_str());
968 
969         NdrParserPluginFactoryBase* pluginFactory =
970             parserPluginType.GetFactory<NdrParserPluginFactoryBase>();
971 
972         if (!TF_VERIFY(pluginFactory)) {
973             continue;
974         }
975 
976         NdrParserPlugin* parserPlugin = pluginFactory->New();
977         _parserPlugins.emplace_back(parserPlugin);
978 
979         for (const TfToken& discoveryType : parserPlugin->GetDiscoveryTypes()) {
980             TF_DEBUG(NDR_DISCOVERY).Msg("  - %s\n", discoveryType.GetText());
981 
982             auto i = _parserPluginMap.insert({discoveryType, parserPlugin});
983             if (!i.second){
984                 const TfType otherType = TfType::Find(*i.first->second);
985                 TF_CODING_ERROR("Plugin type %s claims discovery type '%s' "
986                                 "but that's already claimed by type %s",
987                                 parserPluginType.GetTypeName().c_str(),
988                                 discoveryType.GetText(),
989                                 otherType.GetTypeName().c_str());
990             }
991         }
992     }
993 }
994 
995 void
_RunDiscoveryPlugins(const DiscoveryPluginRefPtrVec & discoveryPlugins)996 NdrRegistry::_RunDiscoveryPlugins(const DiscoveryPluginRefPtrVec& discoveryPlugins)
997 {
998     std::lock_guard<std::mutex> drLock(_discoveryResultMutex);
999 
1000     for (const NdrDiscoveryPluginRefPtr& dp : discoveryPlugins) {
1001         NdrNodeDiscoveryResultVec results =
1002             dp->DiscoverNodes(_DiscoveryContext(*this));
1003         _discoveryResults.reserve(_discoveryResults.size() + results.size());
1004 
1005         for (NdrNodeDiscoveryResult &dr : results) {
1006             _discoveryResultIndicesBySourceType[dr.sourceType].push_back(
1007                 _discoveryResults.size());
1008             _discoveryResults.emplace_back(std::move(dr));
1009         }
1010     }
1011 }
1012 
1013 NdrNodeConstPtr
_InsertNodeIntoCache(const NdrNodeDiscoveryResult & dr)1014 NdrRegistry::_InsertNodeIntoCache(const NdrNodeDiscoveryResult& dr)
1015 {
1016     // Return an existing node in the map if the new node matches the
1017     // identifier AND source type of a node in the map.
1018     std::unique_lock<std::mutex> nmLock(_nodeMapMutex);
1019     NodeMapKey key{dr.identifier, dr.sourceType};
1020     auto it = _nodeMap.find(key);
1021     if (it != _nodeMap.end()) {
1022         // Get the raw ptr from the unique_ptr
1023         return it->second.get();
1024     }
1025 
1026     // Ensure the map is not locked at this point. The parse is the bulk of the
1027     // operation, and concurrency is the most valuable here.
1028     nmLock.unlock();
1029 
1030     // Ensure there is a parser plugin that can handle this node
1031     auto i = _parserPluginMap.find(dr.discoveryType);
1032     if (i == _parserPluginMap.end()) {
1033         TF_DEBUG(NDR_PARSING).Msg("Encountered a node of type [%s], "
1034                                   "with name [%s], but a parser for that type "
1035                                   "could not be found; ignoring.\n",
1036                                   dr.discoveryType.GetText(),  dr.name.c_str());
1037         return nullptr;
1038     }
1039 
1040     NdrNodeUniquePtr newNode = i->second->Parse(dr);
1041 
1042     // Validate the node.
1043     if (!_ValidateNode(newNode, dr)) {
1044         return nullptr;
1045     }
1046 
1047     nmLock.lock();
1048 
1049     NodeMap::const_iterator result =
1050         _nodeMap.emplace(std::move(key), std::move(newNode));
1051 
1052     // Get the unique_ptr from the iterator, then get its raw ptr
1053     return result->second.get();
1054 }
1055 
1056 NdrNodeConstPtrVec
_GetNodeMapAsNodePtrVec(const TfToken & family,NdrVersionFilter filter) const1057 NdrRegistry::_GetNodeMapAsNodePtrVec(
1058     const TfToken& family, NdrVersionFilter filter) const
1059 {
1060     NdrNodeConstPtrVec _nodeVec;
1061 
1062     for (const auto& nodePair : _nodeMap) {
1063         if (_MatchesFamilyAndFilter(nodePair.second, family, filter)) {
1064             _nodeVec.emplace_back(nodePair.second.get());
1065         }
1066     }
1067 
1068     return _nodeVec;
1069 }
1070 
1071 NdrParserPlugin*
_GetParserForDiscoveryType(const TfToken & discoveryType) const1072 NdrRegistry::_GetParserForDiscoveryType(const TfToken& discoveryType) const
1073 {
1074     auto i = _parserPluginMap.find(discoveryType);
1075     return i == _parserPluginMap.end() ? nullptr : i->second;
1076 }
1077 
1078 PXR_NAMESPACE_CLOSE_SCOPE
1079