1 //
2 // Copyright 2020 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 // distributed under the Apache License with the above modification is
18 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 // KIND, either express or implied. See the Apache License for the specific
20 // language governing permissions and limitations under the Apache License.
21 //
22 #include "pxr/pxr.h"
23 
24 #include "pxr/usd/usdShade/connectableAPIBehavior.h"
25 #include "pxr/usd/usdShade/connectableAPI.h"
26 #include "pxr/usd/usdShade/input.h"
27 #include "pxr/usd/usdShade/output.h"
28 #include "pxr/usd/usdShade/tokens.h"
29 
30 #include "pxr/base/js/value.h"
31 #include "pxr/base/plug/notice.h"
32 #include "pxr/base/plug/plugin.h"
33 #include "pxr/base/plug/registry.h"
34 #include "pxr/base/tf/hash.h"
35 #include "pxr/base/tf/instantiateSingleton.h"
36 #include "pxr/base/tf/singleton.h"
37 #include "pxr/base/tf/weakBase.h"
38 
39 #include <tbb/queuing_rw_mutex.h>
40 #include <memory>
41 #include <unordered_map>
42 
43 PXR_NAMESPACE_OPEN_SCOPE
44 
45 ////////////////////////////////////////////////////////////////////////
46 //
47 // UsdShadeConnectableAPIBehavior base implementation
48 //
49 
50 using SharedConnectableAPIBehaviorPtr =
51     std::shared_ptr<UsdShadeConnectableAPIBehavior>;
52 
~UsdShadeConnectableAPIBehavior()53 UsdShadeConnectableAPIBehavior::~UsdShadeConnectableAPIBehavior()
54 {
55 }
56 
57 bool
CanConnectInputToSource(const UsdShadeInput & input,const UsdAttribute & source,std::string * reason) const58 UsdShadeConnectableAPIBehavior::CanConnectInputToSource(
59     const UsdShadeInput &input,
60     const UsdAttribute &source,
61     std::string *reason) const
62 {
63     return _CanConnectInputToSource(input, source, reason);
64 }
65 
66 bool
_CanConnectInputToSource(const UsdShadeInput & input,const UsdAttribute & source,std::string * reason,ConnectableNodeTypes nodeType) const67 UsdShadeConnectableAPIBehavior::_CanConnectInputToSource(
68     const UsdShadeInput &input,
69     const UsdAttribute &source,
70     std::string *reason,
71     ConnectableNodeTypes nodeType) const
72 {
73     if (!input.IsDefined()) {
74         if (reason) {
75             *reason = TfStringPrintf("Invalid input: %s",
76                 input.GetAttr().GetPath().GetText());
77         }
78         return false;
79     }
80 
81     if (!source) {
82         if (reason) {
83             *reason = TfStringPrintf("Invalid source: %s",
84                 source.GetPath().GetText());
85         }
86         return false;
87     }
88 
89 
90     // Ensure that the source prim is the closest ancestor container of the
91     // NodeGraph owning the input.
92     auto encapsulationCheckForInputSources = [&input, &source](
93             std::string *reason) {
94         const SdfPath inputPrimPath = input.GetPrim().GetPath();
95         const SdfPath sourcePrimPath = source.GetPrim().GetPath();
96 
97         if (!UsdShadeConnectableAPI(source.GetPrim()).IsContainer()) {
98             if (reason) {
99                 *reason = TfStringPrintf("Encapsulation check failed - "
100                         "prim '%s' owning the input source '%s' is not a "
101                         "container.", sourcePrimPath.GetText(),
102                         source.GetName().GetText());
103             }
104             return false;
105         }
106         if (inputPrimPath.GetParentPath() != sourcePrimPath) {
107             if (reason) {
108                 *reason = TfStringPrintf("Encapsulation check failed - "
109                         "input source prim '%s' is not the closest ancestor "
110                         "container of the NodeGraph '%s' owning the input "
111                         "attribute '%s'.", sourcePrimPath.GetText(),
112                         inputPrimPath.GetText(), input.GetFullName().GetText());
113             }
114             return false;
115         }
116 
117         return true;
118     };
119 
120     // Ensure that the source prim and input prim are contained by the same
121     // inner most container for all nodes, other than DerivedContainerNodes,
122     // for these make sure source prim is an immediate descendent of the input
123     // prim.
124     auto encapsulationCheckForOutputSources = [&input, &source,
125          &nodeType](std::string *reason) {
126         const SdfPath inputPrimPath = input.GetPrim().GetPath();
127         const SdfPath sourcePrimPath = source.GetPrim().GetPath();
128 
129         switch (nodeType) {
130             case ConnectableNodeTypes::DerivedContainerNodes:
131                 if (!UsdShadeConnectableAPI(input.GetPrim()).IsContainer()) {
132                     if (reason) {
133                         *reason = TfStringPrintf("Encapsulation check failed - "
134                                 "For input's prim type '%s', prim owning the "
135                                 "input '%s' is not a container.",
136                                 input.GetPrim().GetTypeName().GetText(),
137                                 input.GetAttr().GetPath().GetText());
138                     }
139                     return false;
140                 }
141                 if (sourcePrimPath.GetParentPath() != inputPrimPath) {
142                     if (reason) {
143                         *reason = TfStringPrintf("Encapsulation check failed - "
144                                 "For input's prim type '%s', Output source's "
145                                 "prim '%s' is not an immediate descendent of "
146                                 "the input's prim '%s'.",
147                                 input.GetPrim().GetTypeName().GetText(),
148                                 sourcePrimPath.GetText(),
149                                 inputPrimPath.GetText());
150                     }
151                     return false;
152                 }
153                 return true;
154                 break;
155 
156             case ConnectableNodeTypes::BasicNodes:
157             default:
158                 if (!UsdShadeConnectableAPI(input.GetPrim().GetParent()).
159                         IsContainer()) {
160                     if (reason) {
161                         *reason = TfStringPrintf("Encapsulation check failed - "
162                                 "For input's prim type '%s', Immediate ancestor"
163                                 " '%s' for the prim owning the output source "
164                                 "'%s' is not a container.",
165                                 input.GetPrim().GetTypeName().GetText(),
166                                 sourcePrimPath.GetParentPath().GetText(),
167                                 source.GetPath().GetText());
168                     }
169                     return false;
170                 }
171                 if (inputPrimPath.GetParentPath() !=
172                         sourcePrimPath.GetParentPath()) {
173                     if (reason) {
174                         *reason = TfStringPrintf("Encapsulation check failed - "
175                                 "For input's prim type '%s', Input's prim '%s' "
176                                 "and source's prim '%s' are not contained by "
177                                 "the same container prim.",
178                                 input.GetPrim().GetTypeName().GetText(),
179                                 inputPrimPath.GetText(),
180                                 sourcePrimPath.GetText());
181                     }
182                     return false;
183                 }
184                 return true;
185                 break;
186         }
187     };
188 
189     TfToken inputConnectability = input.GetConnectability();
190 
191     const bool requiresEncapsulation = RequiresEncapsulation();
192     if (inputConnectability == UsdShadeTokens->full) {
193         if (UsdShadeInput::IsInput(source)) {
194             if (!requiresEncapsulation ||
195                     encapsulationCheckForInputSources(reason)) {
196                 return true;
197             }
198             return false;
199         }
200         /* source is an output - allow connection */
201         if (!requiresEncapsulation ||
202                 encapsulationCheckForOutputSources(reason)) {
203             return true;
204         }
205         return false;
206     } else if (inputConnectability == UsdShadeTokens->interfaceOnly) {
207         if (UsdShadeInput::IsInput(source)) {
208             TfToken sourceConnectability =
209                 UsdShadeInput(source).GetConnectability();
210             if (sourceConnectability == UsdShadeTokens->interfaceOnly) {
211                 if (!requiresEncapsulation ||
212                         encapsulationCheckForInputSources(reason)) {
213                     return true;
214                 }
215                 return false;
216             } else {
217                 if (reason) {
218                     *reason = "Input connectability is 'interfaceOnly' and " \
219                         "source does not have 'interfaceOnly' connectability.";
220                 }
221                 return false;
222             }
223         } else {
224             if (reason) {
225                 *reason = "Input connectability is 'interfaceOnly' but " \
226                     "source is not an input";
227                 return false;
228             }
229         }
230     } else {
231         if (reason) {
232             *reason = "Input connectability is unspecified";
233         }
234         return false;
235     }
236     return false;
237 }
238 
239 bool
_CanConnectOutputToSource(const UsdShadeOutput & output,const UsdAttribute & source,std::string * reason,ConnectableNodeTypes nodeType) const240 UsdShadeConnectableAPIBehavior::_CanConnectOutputToSource(
241     const UsdShadeOutput &output,
242     const UsdAttribute &source,
243     std::string *reason,
244     ConnectableNodeTypes nodeType) const
245 {
246     // Nodegraphs allow connections to their outputs, but only from
247     // internal nodes.
248     if (!output.IsDefined()) {
249         if (reason) {
250             *reason = TfStringPrintf("Invalid output");
251         }
252         return false;
253     }
254     if (!source) {
255         if (reason) {
256             *reason = TfStringPrintf("Invalid source");
257         }
258         return false;
259     }
260 
261     const SdfPath sourcePrimPath = source.GetPrim().GetPath();
262     const SdfPath outputPrimPath = output.GetPrim().GetPath();
263 
264 
265     const bool requiresEncapsulation = RequiresEncapsulation();
266     if (UsdShadeInput::IsInput(source)) {
267         // passthrough usage is not allowed for DerivedContainerNodes
268         if (nodeType == ConnectableNodeTypes::DerivedContainerNodes) {
269             if (reason) {
270                 *reason = TfStringPrintf("Encapsulation check failed - "
271                         "passthrough usage is not allowed for output prim '%s' "
272                         "of type '%s'.", outputPrimPath.GetText(),
273                         output.GetPrim().GetTypeName().GetText());
274             }
275             return false;
276         }
277         // output can connect to an input of the same container as a
278         // passthrough.
279         if (sourcePrimPath != outputPrimPath) {
280             if (reason) {
281                 *reason = TfStringPrintf("Encapsulation check failed - output "
282                         "'%s' and input source '%s' must be encapsulated by "
283                         "the same container prim",
284                         output.GetAttr().GetPath().GetText(),
285                         source.GetPath().GetText());
286             }
287             return false;
288         }
289         return true;
290     } else { // Source is an output
291         // output can connect to other node's output directly encapsulated by
292         // it, unless explicitly marked to ignore encapsulation rule.
293 
294         if (requiresEncapsulation &&
295                 sourcePrimPath.GetParentPath() != outputPrimPath) {
296             if (reason) {
297                 *reason = TfStringPrintf("Encapsulation check failed - prim "
298                         "owning the output '%s' is not an immediate descendent "
299                         " of the prim owning the output source '%s'.",
300                         output.GetAttr().GetPath().GetText(),
301                         source.GetPath().GetText());
302             }
303             return false;
304         }
305 
306         return true;
307     }
308 }
309 
310 bool
CanConnectOutputToSource(const UsdShadeOutput & output,const UsdAttribute & source,std::string * reason) const311 UsdShadeConnectableAPIBehavior::CanConnectOutputToSource(
312     const UsdShadeOutput &output,
313     const UsdAttribute &source,
314     std::string *reason) const
315 {
316     return _CanConnectOutputToSource(output, source, reason);
317 }
318 
319 bool
IsContainer() const320 UsdShadeConnectableAPIBehavior::IsContainer() const
321 {
322     return _isContainer;
323 }
324 
325 bool
RequiresEncapsulation() const326 UsdShadeConnectableAPIBehavior::RequiresEncapsulation() const
327 {
328     return _requiresEncapsulation;
329 }
330 
331 ////////////////////////////////////////////////////////////////////////
332 //
333 // UsdShadeConnectableAPIBehavior registry
334 //
335 
336 namespace
337 {
338 
339 // This registry is closely modeled after the one in UsdGeomBoundableComputeExtent.
340 class _BehaviorRegistry
341     : public TfWeakBase
342 {
343 private:
344     // A struct to hold the "type identity" of a prim, which is a collection of
345     // its Type and all the ApiSchemas applied to it.
346     // Inspired by UsdPrimTypeInfo::_TypeId
347     struct _PrimTypeId {
348         TfToken primTypeName;
349 
350         TfTokenVector appliedAPISchemas;
351 
352         size_t hash;
353 
354         _PrimTypeId() = default;
355 
_PrimTypeId__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId356         explicit _PrimTypeId(const UsdPrimTypeInfo &primTypeInfo)
357             : primTypeName(primTypeInfo.GetTypeName()),
358               appliedAPISchemas(primTypeInfo.GetAppliedAPISchemas()) {
359                   hash = TfHash()(*this);
360           }
361 
_PrimTypeId__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId362         explicit _PrimTypeId(const TfToken& typeName)
363             : primTypeName(typeName) {
364                 hash = TfHash()(*this);
365         }
366 
_PrimTypeId__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId367         explicit _PrimTypeId(const TfType &type)
368             : primTypeName(UsdSchemaRegistry::GetSchemaTypeName(type)) {
369                 hash = TfHash()(*this);
370         }
371 
372         _PrimTypeId(const _PrimTypeId &primTypeId) = default;
373         _PrimTypeId(_PrimTypeId &&primTypeId) = default;
374 
375         template <class HashState>
TfHashAppend(HashState & h,_PrimTypeId const & primTypeId)376         friend void TfHashAppend(HashState &h, _PrimTypeId const &primTypeId)
377         {
378             h.Append(primTypeId.primTypeName, primTypeId.appliedAPISchemas);
379         }
380 
Hash__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId381         size_t Hash() const
382         {
383             return hash;
384         }
385 
IsEmpty__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId386         bool IsEmpty() const
387         {
388             return primTypeName.IsEmpty() && appliedAPISchemas.empty();
389         }
390 
operator ==__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId391         bool operator==(const _PrimTypeId &other) const
392         {
393             return primTypeName == other.primTypeName &&
394                 appliedAPISchemas == other.appliedAPISchemas;
395         }
396 
operator !=__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId397         bool operator!=(const _PrimTypeId &other) const
398         {
399             return !(*this == other);
400         }
401 
402         // returns a string representation of the PrimTypeId by ";" delimiting
403         // the primTypeName and all the appliedAPISchemas. Useful in debugging
404         // and error handling.
405         std::string
GetString__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId406         GetString() const
407         {
408             static const std::string &DELIM = ";";
409             std::string result = primTypeName.GetString();
410             for(const auto &apiSchema : appliedAPISchemas) {
411                 result += DELIM;
412                 result += apiSchema.GetString();
413             }
414             return result;
415         }
416 
417         struct Hasher
418         {
operator ()__anon6cbc53980311::_BehaviorRegistry::_PrimTypeId::Hasher419             size_t operator()(const _PrimTypeId &id) const
420             {
421                 return id.Hash();
422             }
423         };
424     };
425 
426 public:
GetInstance()427     static _BehaviorRegistry& GetInstance()
428     {
429         return TfSingleton<_BehaviorRegistry>::GetInstance();
430     }
431 
_BehaviorRegistry()432     _BehaviorRegistry()
433         : _initialized(false)
434     {
435         // Calling SubscribeTo may cause functions to be registered
436         // while we're still in the c'tor, so make sure to call
437         // SetInstanceConstructed to allow reentrancy.
438         TfSingleton<_BehaviorRegistry>::SetInstanceConstructed(*this);
439         TfRegistryManager::GetInstance().SubscribeTo<UsdShadeConnectableAPI>();
440 
441         // Mark initialization as completed for waiting consumers.
442         _initialized.store(true, std::memory_order_release);
443 
444         // Register for new plugins being registered so we can invalidate
445         // this registry.
446         //
447         TfNotice::Register(
448             TfCreateWeakPtr(this),
449             &_BehaviorRegistry::_DidRegisterPlugins);
450     }
451 
452     // Cache behavior for _PrimTypeId
453     void
RegisterBehaviorForPrimTypeId(const _PrimTypeId & primTypeId,const SharedConnectableAPIBehaviorPtr & behavior)454     RegisterBehaviorForPrimTypeId(
455             const _PrimTypeId &primTypeId,
456             const SharedConnectableAPIBehaviorPtr &behavior)
457     {
458         bool didInsert = false;
459         {
460             _RWMutex::scoped_lock lock(_primTypeCacheMutex, /* write = */ true);
461             didInsert = _primTypeIdCache.emplace(primTypeId, behavior).second;
462         }
463 
464         if (!didInsert) {
465 
466             TF_CODING_ERROR(
467                 "UsdShade Connectable behavior already registered for "
468                 "primTypeId comprised of '%s' type and apischemas.",
469                 primTypeId.GetString().c_str());
470         }
471     }
472 
473     // Cache behavior for TfType
474     // - Used to register behaviors via TF_REGISTRY_FUNCTION for types.
475     void
RegisterBehaviorForType(const TfType & connectablePrimType,const SharedConnectableAPIBehaviorPtr & behavior)476     RegisterBehaviorForType(
477         const TfType& connectablePrimType,
478         const SharedConnectableAPIBehaviorPtr &behavior)
479     {
480         const _PrimTypeId &primTypeId = _PrimTypeId(connectablePrimType);
481         // Try to insert the behavior in PrimTypeId cache created from the
482         // given type.
483         RegisterBehaviorForPrimTypeId(primTypeId, behavior);
484     }
485 
RegisterPlugConfiguredBehaviorForType(const TfType & type,SharedConnectableAPIBehaviorPtr & behavior)486     void RegisterPlugConfiguredBehaviorForType(
487         const TfType &type, SharedConnectableAPIBehaviorPtr &behavior)
488     {
489         // Lambda which takes a key and returns the metadata value if one
490         // exists corresponding to the key, defaultValue otherwise.
491         auto GetBoolPlugMetadataValue = [&type](const std::string &key,
492                 const bool defaultValue) {
493             PlugRegistry &plugReg = PlugRegistry::GetInstance();
494             const JsValue value = plugReg.GetDataFromPluginMetaData(type, key);
495             if (value.Is<bool>()) {
496                 return value.Get<bool>();
497             }
498             return defaultValue;
499         };
500         bool isContainer =
501             GetBoolPlugMetadataValue("isUsdShadeContainer", false);
502         bool requiresEncapsulation =
503             GetBoolPlugMetadataValue("requiresUsdShadeEncapsulation", true);
504         behavior = SharedConnectableAPIBehaviorPtr(
505                 new UsdShadeConnectableAPIBehavior(
506                     isContainer, requiresEncapsulation));
507         RegisterBehaviorForType(type, behavior);
508     }
509 
510     const UsdShadeConnectableAPIBehavior*
GetBehaviorForPrimTypeId(const _PrimTypeId & primTypeId)511     GetBehaviorForPrimTypeId(const _PrimTypeId &primTypeId)
512     {
513         _WaitUntilInitialized();
514         return _GetBehaviorForPrimTypeId(primTypeId, TfType(), UsdPrim());
515     }
516 
517     const UsdShadeConnectableAPIBehavior*
GetBehaviorForType(const TfType & type)518     GetBehaviorForType(const TfType &type)
519     {
520         _WaitUntilInitialized();
521         return _GetBehaviorForPrimTypeId(_PrimTypeId(type), type, UsdPrim());
522     }
523 
524     bool
HasBehaviorForType(const TfType & type)525     HasBehaviorForType(const TfType& type)
526     {
527         return bool(GetBehaviorForType(type));
528     }
529 
530     const UsdShadeConnectableAPIBehavior*
GetBehavior(const UsdPrim & prim)531     GetBehavior(const UsdPrim& prim)
532     {
533         _WaitUntilInitialized();
534 
535         // Get the actual schema type from the prim definition.
536         const TfType &primSchemaType = prim.GetPrimTypeInfo().GetSchemaType();
537         if (!primSchemaType) {
538             TF_CODING_ERROR(
539                 "Could not find prim type '%s' for prim %s",
540                 prim.GetTypeName().GetText(), UsdDescribe(prim).c_str());
541             return nullptr;
542         }
543 
544         const _PrimTypeId &primTypeId = _PrimTypeId(prim.GetPrimTypeInfo());
545         return _GetBehaviorForPrimTypeId(primTypeId, primSchemaType, prim);
546     }
547 
548 private:
549     // Note that below functionality is such that the order of precedence for
550     // which a behavior is chosen is:
551     // 1. Behavior defined on an authored API schemas, wins over
552     // 2. Behavior defined for a prim type, wins over
553     // 3. Behavior defined for the prim's ancestor types, wins over
554     // 4. Behavior defined for any built-in API schemas.
555     // 5. If no Behavior is found but an api schema adds
556     //    providesUsdShadeConnectableAPIBehavior plug metadata then a default
557     //    behavior is registered for the primTypeId.
558     //
559     const UsdShadeConnectableAPIBehavior*
_GetBehaviorForPrimTypeId(const _PrimTypeId & primTypeId,TfType primSchemaType,const UsdPrim & prim)560     _GetBehaviorForPrimTypeId(const _PrimTypeId &primTypeId,
561                               TfType primSchemaType,
562                               const UsdPrim& prim)
563     {
564         SharedConnectableAPIBehaviorPtr behavior;
565 
566         // Has a behavior cached for this primTypeId, if so fetch it and return!
567         if (_FindBehaviorForPrimTypeId(primTypeId, &behavior)) {
568             return behavior.get();
569         }
570 
571         // Look up the the schema type if we don't have it already.
572         // This is delayed until now in order to make the above cache
573         // check as fast as possible.
574         if (primSchemaType.IsUnknown()) {
575             primSchemaType =
576                 TfType::FindByName(primTypeId.primTypeName.GetString());
577             if (primSchemaType.IsUnknown()) {
578                 return nullptr;
579             }
580         }
581 
582         // If a behavior is not found for primTypeId, we try to look for a
583         // registered behavior in prim's ancestor types.
584         bool foundBehaviorInAncestorType = false;
585         std::vector<TfType> primSchemaTypeAndBases;
586         primSchemaType.GetAllAncestorTypes(&primSchemaTypeAndBases);
587         auto i = primSchemaTypeAndBases.cbegin();
588         for (auto e = primSchemaTypeAndBases.cend(); i != e; ++i) {
589             const TfType& type = *i;
590             if (_FindBehaviorForType(type, &behavior)) {
591                 foundBehaviorInAncestorType = true;
592                 break;
593             }
594 
595             if (_LoadPluginDefiningBehaviorForType(type)) {
596                 // If we loaded the plugin for this type, a new function may
597                 // have been registered so look again.
598                 if (!_FindBehaviorForType(type, &behavior)) {
599                     // If no registered behavior is found, then register a
600                     // behavior via plugInfo configuration, since this types
601                     // plug has a providesUsdShadeConnectableAPIBehavior
602                     RegisterPlugConfiguredBehaviorForType(type, behavior);
603                 }
604                 foundBehaviorInAncestorType = true;
605                 break;
606             }
607         }
608         // If a behavior is found on primType's ancestor, we can safely
609         // cache this behavior for all types between this prim's type and
610         // the ancestor type for which the behavior is found.
611         if (foundBehaviorInAncestorType) {
612             // Note that we need to atomically add insert behavior for all
613             // ancestor types, hence acquiring a write lock here.
614             _RWMutex::scoped_lock lock(_primTypeCacheMutex, /* write = */ true);
615 
616             // behavior should point to the functions to use for all types
617             // in the range [primSchemaTypeAndBases.begin(), i).
618             for (auto it = primSchemaTypeAndBases.cbegin(); it != i; ++it) {
619                 const _PrimTypeId &ancestorPrimTypeId = _PrimTypeId(*it);
620                 _primTypeIdCache.emplace(ancestorPrimTypeId, behavior);
621             }
622         }
623 
624         // A behavior is found for the type in its lineage -- look for
625         // overriding behavior on all explicitly authored apiSchemas on the
626         // prim. If found cache this overriding behavior against the primTypeId.
627         if (behavior) {
628             for (auto& appliedSchema : primTypeId.appliedAPISchemas) {
629                 const TfType &appliedSchemaType =
630                     UsdSchemaRegistry::GetAPITypeFromSchemaTypeName(
631                             appliedSchema);
632                 // Override the prim type registered behavior if any of the
633                 // authored apiSchemas (in strength order) provides a
634                 // UsdShadeConnectableAPIBehavior
635                 SharedConnectableAPIBehaviorPtr apiBehavior;
636                 if (_FindBehaviorForApiSchema(appliedSchemaType, apiBehavior)) {
637                     behavior = apiBehavior;
638                     RegisterBehaviorForPrimTypeId(primTypeId, behavior);
639                     break;
640                 }
641             }
642             // If no behavior was found for any of the apischemas on the prim,
643             // we can return the behavior found on the ancestor. Note that we
644             // have already inserted the behavior for all types between this
645             // prim's type and the ancestor for which behavior was found to the
646             // cache.
647             return behavior.get();
648         }
649 
650         // No behavior was found to be registered on prim type or primTypeId,
651         // lookup all apiSchemas and if found, register it against primTypeId
652         // in the _primTypeIdCache. Note that codeless api schemas could
653         // contain providesUsdShadeConnectableAPIBehavior plug metadata
654         // without providing a c++ Behavior implementation, for such applied
655         // schemas, a default UsdShadeConnectableAPIBehavior is created and
656         // registered/cached with the appliedSchemaType and the primTypeId.
657         if (prim) {
658             for (auto& appliedSchema : prim.GetAppliedSchemas()) {
659                 const TfType &appliedSchemaType =
660                     UsdSchemaRegistry::GetAPITypeFromSchemaTypeName(
661                             appliedSchema);
662                 if (_FindBehaviorForApiSchema(appliedSchemaType, behavior)) {
663                     RegisterBehaviorForPrimTypeId(primTypeId, behavior);
664                     break;
665                 }
666             }
667         }
668 
669         // If behavior is still not found, the primTypeId is lacking one, cache
670         // a null behavior for this primTypeId
671         if (!behavior) {
672             RegisterBehaviorForPrimTypeId(primTypeId, behavior);
673         }
674 
675         return behavior.get();
676     }
677 
678     // Wait until initialization of the singleton is completed.
_WaitUntilInitialized()679     void _WaitUntilInitialized()
680     {
681         while (ARCH_UNLIKELY(!_initialized.load(std::memory_order_acquire))) {
682             std::this_thread::yield();
683         }
684     }
685 
686     // Load the plugin for the given type if it supplies connectable behavior.
_LoadPluginDefiningBehaviorForType(const TfType & type) const687     bool _LoadPluginDefiningBehaviorForType(const TfType& type) const
688     {
689         PlugRegistry& plugReg = PlugRegistry::GetInstance();
690 
691         const JsValue providesUsdShadeConnectableAPIBehavior =
692             plugReg.GetDataFromPluginMetaData(type,
693                     "providesUsdShadeConnectableAPIBehavior");
694         if (!providesUsdShadeConnectableAPIBehavior.Is<bool>() ||
695             !providesUsdShadeConnectableAPIBehavior.Get<bool>()) {
696             return false;
697         }
698 
699         const PlugPluginPtr pluginForType = plugReg.GetPluginForType(type);
700         if (!pluginForType) {
701             TF_CODING_ERROR(
702                 "Could not find plugin for '%s'", type.GetTypeName().c_str());
703             return false;
704         }
705 
706         return pluginForType->Load();
707     }
708 
_DidRegisterPlugins(const PlugNotice::DidRegisterPlugins & n)709     void _DidRegisterPlugins(const PlugNotice::DidRegisterPlugins& n)
710     {
711         // Erase the entries in _primTypeIdCache which have a null behavior
712         // registered, since newly-registered plugins may provide valid
713         // behavior for these primTypeId entries.
714         // Note that we retain entries which have valid connectableAPIBehavior
715         // defined.
716         //
717         {
718             _RWMutex::scoped_lock lock(_primTypeCacheMutex, /* write = */ true);
719             _PrimTypeIdCache::iterator itr = _primTypeIdCache.begin(),
720                 end = _primTypeIdCache.end();
721             while (itr != end) {
722                 if (!itr->second) {
723                     itr = _primTypeIdCache.erase(itr);
724                     end = _primTypeIdCache.end();
725                     continue;
726                 }
727                 itr++;
728             }
729         }
730     }
731 
_FindBehaviorForPrimTypeId(const _PrimTypeId & primTypeId,SharedConnectableAPIBehaviorPtr * behavior) const732     bool _FindBehaviorForPrimTypeId(
733         const _PrimTypeId &primTypeId,
734         SharedConnectableAPIBehaviorPtr *behavior) const
735     {
736         _RWMutex::scoped_lock lock(_primTypeCacheMutex, /* write = */ false);
737         return TfMapLookup(_primTypeIdCache, primTypeId, behavior);
738     }
739 
_FindBehaviorForType(const TfType & type,SharedConnectableAPIBehaviorPtr * behavior) const740     bool _FindBehaviorForType(
741         const TfType& type,
742         SharedConnectableAPIBehaviorPtr *behavior) const
743     {
744         return _FindBehaviorForPrimTypeId(_PrimTypeId(type), behavior);
745     }
746 
_FindBehaviorForApiSchema(const TfType & appliedSchemaType,SharedConnectableAPIBehaviorPtr & apiBehavior)747     bool _FindBehaviorForApiSchema(const TfType &appliedSchemaType,
748             SharedConnectableAPIBehaviorPtr &apiBehavior)
749     {
750         if (_LoadPluginDefiningBehaviorForType(appliedSchemaType)) {
751             if (!_FindBehaviorForType(appliedSchemaType, &apiBehavior)) {
752                 // If a behavior is not found/registered (but an
753                 // appliedSchema specified its implementation, create a
754                 // default behavior here and register it against this
755                 // primTypeId.
756                 RegisterPlugConfiguredBehaviorForType(
757                         appliedSchemaType, apiBehavior);
758             }
759             return true;
760         }
761         return false;
762     }
763 
764 private:
765     using _RWMutex = tbb::queuing_rw_mutex;
766     mutable _RWMutex _primTypeCacheMutex;
767 
768 
769     using _PrimTypeIdCache =
770         std::unordered_map<_PrimTypeId, SharedConnectableAPIBehaviorPtr,
771             _PrimTypeId::Hasher>;
772     _PrimTypeIdCache _primTypeIdCache;
773 
774     std::atomic<bool> _initialized;
775 };
776 
777 }
778 
779 TF_INSTANTIATE_SINGLETON(_BehaviorRegistry);
780 
781 void
UsdShadeRegisterConnectableAPIBehavior(const TfType & connectablePrimType,const SharedConnectableAPIBehaviorPtr & behavior)782 UsdShadeRegisterConnectableAPIBehavior(
783     const TfType& connectablePrimType,
784     const SharedConnectableAPIBehaviorPtr &behavior)
785 {
786     if (!behavior || connectablePrimType.IsUnknown()) {
787         TF_CODING_ERROR(
788             "Invalid behavior registration for prim type '%s'",
789             connectablePrimType.GetTypeName().c_str());
790         return;
791     }
792 
793     _BehaviorRegistry::GetInstance().RegisterBehaviorForType(
794             connectablePrimType, behavior);
795 }
796 
797 ////////////////////////////////////////////////////////////////////////
798 //
799 // UsdShadeConnectableAPI implementations using registered behavior
800 //
801 
802 /* virtual */
803 bool
_IsCompatible() const804 UsdShadeConnectableAPI::_IsCompatible() const
805 {
806     if (!UsdAPISchemaBase::_IsCompatible() )
807         return false;
808 
809     // The API is compatible as long as its behavior has been defined.
810     return bool(_BehaviorRegistry::GetInstance().GetBehavior(GetPrim()));
811 }
812 
813 bool
CanConnect(const UsdShadeInput & input,const UsdAttribute & source)814 UsdShadeConnectableAPI::CanConnect(
815     const UsdShadeInput &input,
816     const UsdAttribute &source)
817 {
818     // The reason why a connection can't be made isn't exposed currently.
819     // We may want to expose it in the future, especially when we have
820     // validation in USD.
821     std::string reason;
822     if (const UsdShadeConnectableAPIBehavior *behavior =
823         _BehaviorRegistry::GetInstance().GetBehavior(input.GetPrim())) {
824         return behavior->CanConnectInputToSource(input, source, &reason);
825     }
826     return false;
827 }
828 
829 bool
CanConnect(const UsdShadeOutput & output,const UsdAttribute & source)830 UsdShadeConnectableAPI::CanConnect(
831     const UsdShadeOutput &output,
832     const UsdAttribute &source)
833 {
834     // The reason why a connection can't be made isn't exposed currently.
835     // We may want to expose it in the future, especially when we have
836     // validation in USD.
837     std::string reason;
838     if (const UsdShadeConnectableAPIBehavior *behavior =
839         _BehaviorRegistry::GetInstance().GetBehavior(output.GetPrim())) {
840         return behavior->CanConnectOutputToSource(output, source, &reason);
841     }
842     return false;
843 }
844 
845 /* static */
846 bool
HasConnectableAPI(const TfType & schemaType)847 UsdShadeConnectableAPI::HasConnectableAPI(const TfType& schemaType)
848 {
849     return _BehaviorRegistry::GetInstance().HasBehaviorForType(schemaType);
850 }
851 
852 bool
IsContainer() const853 UsdShadeConnectableAPI::IsContainer() const
854 {
855     if (const UsdShadeConnectableAPIBehavior *behavior =
856         _BehaviorRegistry::GetInstance().GetBehavior(GetPrim())) {
857         return behavior->IsContainer();
858     }
859     return false;
860 }
861 
862 bool
RequiresEncapsulation() const863 UsdShadeConnectableAPI::RequiresEncapsulation() const
864 {
865     if (const UsdShadeConnectableAPIBehavior *behavior =
866         _BehaviorRegistry::GetInstance().GetBehavior(GetPrim())) {
867         return behavior->RequiresEncapsulation();
868     }
869     return false;
870 }
871 
872 PXR_NAMESPACE_CLOSE_SCOPE
873