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