1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24
25 #include "pxr/pxr.h"
26
27 #include "pxr/base/tf/type.h"
28
29 #include "pxr/base/arch/demangle.h"
30 #include "pxr/base/tf/hash.h"
31 #include "pxr/base/tf/hashmap.h"
32 #include "pxr/base/tf/instantiateSingleton.h"
33 #include "pxr/base/tf/iterator.h"
34 #include "pxr/base/tf/mallocTag.h"
35 #include "pxr/base/tf/scopeDescription.h"
36 #include "pxr/base/tf/singleton.h"
37 #include "pxr/base/tf/stl.h"
38 #include "pxr/base/tf/stringUtils.h"
39 #include "pxr/base/tf/typeInfoMap.h"
40 #include "pxr/base/tf/typeNotice.h"
41
42 #ifdef PXR_PYTHON_SUPPORT_ENABLED
43 // XXX: This include is a hack to avoid build errors due to
44 // incompatible macro definitions in pyport.h on macOS.
45 #include <locale>
46 #include "pxr/base/tf/cxxCast.h"
47 #include "pxr/base/tf/pyLock.h"
48 #include "pxr/base/tf/pyObjWrapper.h"
49 #include "pxr/base/tf/pyObjectFinder.h"
50 #include "pxr/base/tf/pyUtils.h"
51 #endif // PXR_PYTHON_SUPPORT_ENABLED
52
53 #include <boost/noncopyable.hpp>
54 #include <boost/optional.hpp>
55 #include <boost/utility/in_place_factory.hpp>
56
57 #include <tbb/spin_rw_mutex.h>
58
59 #include <atomic>
60 #include <algorithm>
61 #include <iostream>
62 #include <map>
63 #include <memory>
64 #include <vector>
65
66 #include <thread>
67
68 using std::map;
69 using std::pair;
70 using std::string;
71 using std::vector;
72
73 PXR_NAMESPACE_OPEN_SCOPE
74
75 typedef vector<TfType> TypeVector;
76
77 using RWMutex = tbb::spin_rw_mutex;
78 using ScopedLock = tbb::spin_rw_mutex::scoped_lock;
79
~FactoryBase()80 TfType::FactoryBase::~FactoryBase()
81 {
82 }
83
84 #ifdef PXR_PYTHON_SUPPORT_ENABLED
~PyPolymorphicBase()85 TfType::PyPolymorphicBase::~PyPolymorphicBase()
86 {
87 }
88 #endif // PXR_PYTHON_SUPPORT_ENABLED
89
90 // Stored data for a TfType.
91 // A unique instance of _TypeInfo is allocated for every type declared.
92 //
93 struct TfType::_TypeInfo : boost::noncopyable
94 {
95 typedef TfHashMap<string, TfType::_TypeInfo*, TfHash> NameToTypeMap;
96 typedef TfHashMap<
97 TfType::_TypeInfo*, vector<string>, TfHash> TypeToNamesMap;
98 typedef TfHashMap<string, TfType, TfHash> DerivedByNameCache;
99
100 // Unique TfType instance, for returning const references to.
101 TfType canonicalTfType;
102
103 // Unique type name.
104 const string typeName;
105
106 // Callback invoked to define this type when first required.
107 TfType::DefinitionCallback definitionCallback;
108
109 // C++ type_info. NULL if no C++ type has been defined.
110 std::atomic<std::type_info const *> typeInfo;
111
112 // The size returned by sizeof(type).
113 size_t sizeofType;
114
115 #ifdef PXR_PYTHON_SUPPORT_ENABLED
116 // Python class handle.
117 // We use handle<> rather than boost::python::object in case Python
118 // has not yet been initialized.
119 boost::python::handle<> pyClass;
120 #endif // PXR_PYTHON_SUPPORT_ENABLED
121
122 // Direct base types.
123 TypeVector baseTypes;
124
125 // Direct derived types.
126 TypeVector derivedTypes;
127
128 // Factory.
129 std::unique_ptr<TfType::FactoryBase> factory;
130
131 // Map of derived type aliases to derived types.
132 boost::optional<NameToTypeMap> aliasToDerivedTypeMap;
133 // Reverse map of derived types to their aliases.
134 boost::optional<TypeToNamesMap> derivedTypeToAliasesMap;
135
136 // Map of functions for converting to other types.
137 // This map is keyed by type_info and not TfType because the TfTypes
138 // may not have been defined yet at the time we are adding castFuncs.
139 // It is expected that the entries here will ultimately have matching
140 // entries in our baseTypes, although that is not enforced.
141 vector<pair<std::type_info const *, TfType::_CastFunction> > castFuncs;
142
143 std::unique_ptr<DerivedByNameCache> derivedByNameCache;
144
145 // Traits about the static type.
146 bool isPodType;
147 bool isEnumType;
148
149 // True if we have sent a TfTypeWasDeclaredNotice for this type.
150 bool hasSentNotice;
151
152 mutable RWMutex mutex;
153
154 ////////////////////////////////////////////////////////////////////////
155
156 // A type is "defined" as soon as it has either type_info or a
157 // Python class object.
IsDefinedTfType::_TypeInfo158 inline bool IsDefined() {
159 #ifdef PXR_PYTHON_SUPPORT_ENABLED
160 return typeInfo.load() != nullptr || pyClass.get();
161 #else
162 return typeInfo.load() != nullptr;
163 #endif // PXR_PYTHON_SUPPORT_ENABLED
164 }
165
166 // Caller must hold a write lock on mutex.
SetCastFuncTfType::_TypeInfo167 void SetCastFunc(std::type_info const &baseType,
168 TfType::_CastFunction const &func) {
169 // check for existing func.
170 for (size_t i = 0; i < castFuncs.size(); ++i) {
171 if (baseType == *castFuncs[i].first) {
172 castFuncs[i].second = func;
173 return;
174 }
175 }
176 // need to add a new func.
177 castFuncs.push_back(std::make_pair(&baseType, func));
178 }
179
180 // Caller must hold at least a read lock on mutex.
GetCastFuncTfType::_TypeInfo181 TfType::_CastFunction *GetCastFunc(std::type_info const &baseType) {
182 for (size_t i = 0; i < castFuncs.size(); ++i)
183 if (TfSafeTypeCompare(baseType, *castFuncs[i].first))
184 return &castFuncs[i].second;
185 return 0;
186 }
187
188 // Caller must hold at least a read lock on mutex.
FindByAliasTfType::_TypeInfo189 _TypeInfo *FindByAlias(std::string const &alias) const {
190 if (aliasToDerivedTypeMap) {
191 auto it = aliasToDerivedTypeMap->find(alias);
192 return it != aliasToDerivedTypeMap->end() ? it->second : nullptr;
193 }
194 return nullptr;
195 }
196
197 // Allocate an empty (undefined) _TypeInfo with the given typeName.
_TypeInfoTfType::_TypeInfo198 _TypeInfo(const string &newTypeName) :
199 canonicalTfType(this),
200 typeName(newTypeName),
201 definitionCallback(nullptr),
202 typeInfo(nullptr),
203 sizeofType(0),
204 isPodType(false),
205 isEnumType(false),
206 hasSentNotice(false)
207 {
208 }
209 };
210
211 #ifdef PXR_PYTHON_SUPPORT_ENABLED
212 // Comparison for boost::python::handle.
213 struct Tf_PyHandleLess
214 {
operator ()Tf_PyHandleLess215 bool operator()(const boost::python::handle<> &lhs,
216 const boost::python::handle<> &rhs) const {
217 return lhs.get() < rhs.get();
218 }
219 };
220 #endif // PXR_PYTHON_SUPPORT_ENABLED
221
222 // Registry for _TypeInfos.
223 //
224 class Tf_TypeRegistry : boost::noncopyable
225 {
226 public:
GetInstance()227 static Tf_TypeRegistry& GetInstance() {
228 return TfSingleton<Tf_TypeRegistry>::GetInstance();
229 }
230
GetMutex() const231 RWMutex &GetMutex() const { return _mutex; }
232
WaitForInitializingThread() const233 inline void WaitForInitializingThread() const {
234 // If we are the initializing thread or if the registry is initialized,
235 // we don't have to wait.
236 std::thread::id initId = _initializingThread;
237 if (initId == std::thread::id() ||
238 initId == std::this_thread::get_id()) {
239 return;
240 }
241
242 // Otherwise spin until initialization is complete.
243 while (_initializingThread != std::thread::id()) {
244 std::this_thread::yield();
245 }
246 }
247
248 // Note, callers must hold the registry lock for writing, and base's lock
249 // for writing, but need not hold derived's lock.
AddTypeAlias(TfType::_TypeInfo * base,TfType::_TypeInfo * derived,const string & alias,string * errMsg)250 void AddTypeAlias(TfType::_TypeInfo *base, TfType::_TypeInfo *derived,
251 const string &alias, string *errMsg) {
252 // Aliases cannot conflict with other aliases under the same base.
253 if (base->aliasToDerivedTypeMap) {
254 TfType::_TypeInfo::NameToTypeMap::const_iterator it =
255 base->aliasToDerivedTypeMap->find(alias);
256 if (it != base->aliasToDerivedTypeMap->end()) {
257 if (it->second == derived) {
258 // Alias already exists; no change.
259 return;
260 } else {
261 *errMsg = TfStringPrintf(
262 "Cannot set alias '%s' under '%s', because "
263 "it is already set to '%s', not '%s'.",
264 alias.c_str(),
265 base->typeName.c_str(),
266 it->second->typeName.c_str(),
267 derived->typeName.c_str());
268 return;
269 }
270 }
271 }
272 // Aliases cannot conflict with typeNames that are derived from the
273 // same base, either.
274 const auto it = _typeNameToTypeMap.find(alias);
275 if (it != _typeNameToTypeMap.end() &&
276 it->second->canonicalTfType._IsAImpl(base->canonicalTfType)) {
277 *errMsg = TfStringPrintf(
278 "There already is a type named '%s' derived from base "
279 "type '%s'; cannot create an alias of the same name.",
280 alias.c_str(), base->typeName.c_str());
281 return;
282 }
283
284 if (!base->aliasToDerivedTypeMap)
285 base->aliasToDerivedTypeMap = boost::in_place(0);
286 (*base->aliasToDerivedTypeMap)[alias] = derived;
287
288 if (!base->derivedTypeToAliasesMap)
289 base->derivedTypeToAliasesMap = boost::in_place(0);
290 (*base->derivedTypeToAliasesMap)[derived].push_back(alias);
291 }
292
NewTypeInfo(const string & typeName)293 TfType::_TypeInfo *NewTypeInfo(const string &typeName) {
294 TfType::_TypeInfo *info = new TfType::_TypeInfo(typeName);
295 _typeNameToTypeMap[typeName] = info;
296 return info;
297 }
298
SetTypeInfo(TfType::_TypeInfo * info,const std::type_info & typeInfo,size_t sizeofType,bool isPodType,bool isEnumType)299 void SetTypeInfo(TfType::_TypeInfo *info, const std::type_info & typeInfo,
300 size_t sizeofType, bool isPodType, bool isEnumType) {
301 info->typeInfo = &typeInfo;
302 info->sizeofType = sizeofType;
303 info->isPodType = isPodType;
304 info->isEnumType = isEnumType;
305 _typeInfoMap.Set(typeInfo, info);
306 }
307
308 #ifdef PXR_PYTHON_SUPPORT_ENABLED
SetPythonClass(TfType::_TypeInfo * info,const boost::python::object & classObj)309 void SetPythonClass(TfType::_TypeInfo *info,
310 const boost::python::object & classObj) {
311 // Hold a reference to this PyObject in our map.
312 boost::python::handle<> handle(
313 boost::python::borrowed(classObj.ptr()));
314
315 info->pyClass = handle;
316 _pyClassMap[handle] = info;
317
318 // Do not overwrite the size of a C++ type.
319 if (!info->sizeofType) {
320 info->sizeofType = TfSizeofType<boost::python::object>::value;
321 }
322 }
323 #endif // PXR_PYTHON_SUPPORT_ENABLED
324
GetUnknownType() const325 TfType::_TypeInfo *GetUnknownType() const { return _unknownTypeInfo; }
326
GetRoot() const327 TfType::_TypeInfo *GetRoot() const { return _rootTypeInfo; }
328
FindByName(const string & name) const329 TfType::_TypeInfo *FindByName(const string &name) const {
330 auto it = _typeNameToTypeMap.find(name);
331 return it != _typeNameToTypeMap.end() ? it->second : nullptr;
332 }
333
334 template <class Upgrader>
335 TfType::_TypeInfo *
FindByTypeid(const std::type_info & typeInfo,Upgrader upgrader)336 FindByTypeid(const std::type_info &typeInfo, Upgrader upgrader) {
337 TfType::_TypeInfo **info = _typeInfoMap.Find(typeInfo, upgrader);
338 return info ? *info : nullptr;
339 }
340
341 #ifdef PXR_PYTHON_SUPPORT_ENABLED
342 TfType::_TypeInfo *
FindByPythonClass(const boost::python::object & classObj) const343 FindByPythonClass(const boost::python::object &classObj) const {
344 boost::python::handle<> handle(
345 boost::python::borrowed(classObj.ptr()));
346 auto it = _pyClassMap.find(handle);
347 return it != _pyClassMap.end() ? it->second : nullptr;
348 }
349 #endif // PXR_PYTHON_SUPPORT_ENABLED
350
351 private:
352 Tf_TypeRegistry();
353
354 mutable RWMutex _mutex;
355
356 // The thread that is currently performing initialization. This is set to a
357 // default-constructed thread::id when initialization is complete.
358 mutable std::atomic<std::thread::id> _initializingThread;
359
360 // Map of typeName to _TypeInfo*.
361 // This holds all declared types, by unique typename.
362 TfType::_TypeInfo::NameToTypeMap _typeNameToTypeMap;
363
364 // Map of type_info to _TypeInfo*.
365 // This holds info for types that have been defined as C++ types.
366 // XXX: change this to regular hash table?
367 TfTypeInfoMap<TfType::_TypeInfo*> _typeInfoMap;
368
369 #ifdef PXR_PYTHON_SUPPORT_ENABLED
370 // Map of python class handles to _TypeInfo*.
371 typedef map<boost::python::handle<>,
372 TfType::_TypeInfo *, Tf_PyHandleLess> PyClassMap;
373 PyClassMap _pyClassMap;
374 #endif // PXR_PYTHON_SUPPORT_ENABLED
375
376 // _TypeInfo for Unknown type
377 TfType::_TypeInfo *_unknownTypeInfo;
378 // _TypeInfo for Root type
379 TfType::_TypeInfo *_rootTypeInfo;
380
381 // Set true if we should send notification.
382 bool _sendDeclaredNotification;
383
384 friend class TfSingleton<Tf_TypeRegistry>;
385 friend class TfType;
386 };
387
388 TF_INSTANTIATE_SINGLETON(Tf_TypeRegistry);
389
390 // This type is used as the unknown type. Previously, 'void' was used for
391 // that purpose, but clients want to call TfType::Find<void>();.
392 struct _TfUnknownType {};
393
Tf_TypeRegistry()394 Tf_TypeRegistry::Tf_TypeRegistry() :
395 _unknownTypeInfo(0),
396 _rootTypeInfo(0),
397 _sendDeclaredNotification(false)
398 {
399 // Register root type
400 _rootTypeInfo = NewTypeInfo("TfType::_Root");
401
402 // Register unknown type
403 _unknownTypeInfo = NewTypeInfo("TfType::_Unknown");
404 SetTypeInfo(_unknownTypeInfo, typeid(_TfUnknownType),
405 /*sizeofType=*/0, /*isPodType=*/false, /*isEnumType=*/false);
406
407 // Put the registry into an "initializing" state so that racing to get the
408 // singleton instance (which will start happening immediately after calling
409 // SetInstanceConstructed) will wait until initial type registrations are
410 // completed. Note that we only allow *this* thread to query the registry
411 // until initialization is finished. Others will wait.
412 _initializingThread = std::this_thread::get_id();
413 TfSingleton<Tf_TypeRegistry>::SetInstanceConstructed(*this);
414
415 // We send TfTypeWasDeclaredNotice() when a type is first declared with
416 // bases. Because TfNotice delivery uses TfType, we first register both
417 // TfNotice and TfTypeWasDeclaredNotice -- without sending
418 // TfTypeWasDeclaredNotice for them -- before subscribing to the TfType
419 // registry.
420 TfType::Define<TfNotice>();
421 TfType::Define<TfTypeWasDeclaredNotice, TfType::Bases<TfNotice> >();
422
423 // From this point on, we'll send notices as new types are discovered.
424 _sendDeclaredNotification = true;
425
426 try {
427 TfRegistryManager::GetInstance().SubscribeTo<TfType>();
428 _initializingThread = std::thread::id();
429 } catch (...) {
430 // Ensure we mark initialization completed in the face of an exception.
431 _initializingThread = std::thread::id();
432 throw;
433 }
434 }
435
436 ////////////////////////////////////////////////////////////////////////
437
TfType()438 TfType::TfType() : _info(Tf_TypeRegistry::GetInstance().GetUnknownType())
439 {
440 }
441
442 TfType const&
GetRoot()443 TfType::GetRoot()
444 {
445 return Tf_TypeRegistry::GetInstance().GetRoot()->canonicalTfType;
446 }
447
448 TfType const&
GetCanonicalType() const449 TfType::GetCanonicalType() const
450 {
451 return _info->canonicalTfType;
452 }
453
454 TfType const&
FindByName(const string & name)455 TfType::FindByName(const string &name)
456 {
457 return GetRoot().FindDerivedByName(name);
458 }
459
460 TfType const&
FindDerivedByName(const string & name) const461 TfType::FindDerivedByName(const string &name) const
462 {
463 if (IsUnknown())
464 return GetUnknownType();
465
466 TfType result;
467
468 // Note that we cache results in derivedByNameCache, and we never invalidate
469 // this cache. This works because 1) we never remove types and type
470 // information from TfType's data structures and 2) we only cache if we find
471 // a valid type.
472 ScopedLock thisInfoLock(_info->mutex, /*write=*/false);
473 if (ARCH_LIKELY(_info->derivedByNameCache &&
474 TfMapLookup(*(_info->derivedByNameCache), name, &result))) {
475 // Cache hit. We're done.
476 return result._info->canonicalTfType;
477 }
478 // Look for a type derived from *this, and has the given name as an alias.
479 if (TfType::_TypeInfo *foundInfo = _info->FindByAlias(name)) {
480 result = TfType(foundInfo);
481 }
482 // Finished reading _info data.
483 thisInfoLock.release();
484
485 // If we didn't find an alias we now look in the registry.
486 if (!result) {
487 const auto &r = Tf_TypeRegistry::GetInstance();
488 r.WaitForInitializingThread();
489 ScopedLock regLock(r.GetMutex(), /*write=*/false);
490 TfType::_TypeInfo *foundInfo = r.FindByName(name);
491 regLock.release();
492 if (foundInfo) {
493 // Next look for a type with the given typename. If a type was
494 // found, verify that it derives from *this.
495 result = TfType(foundInfo);
496 if (!result.IsA(*this))
497 result = TfType();
498 }
499 }
500
501 // Populate cache.
502 if (result) {
503 // It's possible that some other thread has done this already, but it
504 // will be the same result so it's okay to do redundantly in that case.
505 thisInfoLock.acquire(_info->mutex, /*write=*/true);
506 if (!_info->derivedByNameCache) {
507 _info->derivedByNameCache.
508 reset(new _TypeInfo::DerivedByNameCache(0));
509 }
510 _info->derivedByNameCache->insert(make_pair(name, result));
511 }
512
513 return result._info->canonicalTfType;
514 }
515
516 TfType const&
GetUnknownType()517 TfType::GetUnknownType()
518 {
519 return Tf_TypeRegistry::GetInstance().GetUnknownType()->canonicalTfType;
520 }
521
522 TfType const&
_FindByTypeid(const std::type_info & typeInfo)523 TfType::_FindByTypeid(const std::type_info &typeInfo)
524 {
525 // Functor to upgrade the read lock to a write lock.
526 struct WriteUpgrader {
527 WriteUpgrader(ScopedLock& lock) : lock(lock) { }
528 void operator()() { lock.upgrade_to_writer(); }
529 ScopedLock& lock;
530 };
531
532 auto &r = Tf_TypeRegistry::GetInstance();
533 r.WaitForInitializingThread();
534
535 ScopedLock readLock(r.GetMutex(), /*write=*/false);
536 TfType::_TypeInfo *info = r.FindByTypeid(typeInfo, WriteUpgrader(readLock));
537
538 if (ARCH_LIKELY(info)) {
539 return info->canonicalTfType;
540 }
541 // It's possible that this type is only declared and not yet defined. In
542 // that case we will fail to find it by type_info, so attempt to find the
543 // type by name instead.
544 return FindByName(GetCanonicalTypeName(typeInfo));
545 }
546
547 #ifdef PXR_PYTHON_SUPPORT_ENABLED
548 TfType const&
FindByPythonClass(const TfPyObjWrapper & classObj)549 TfType::FindByPythonClass(const TfPyObjWrapper & classObj)
550 {
551 const auto &r = Tf_TypeRegistry::GetInstance();
552 r.WaitForInitializingThread();
553
554 ScopedLock readLock(r.GetMutex(), /*write=*/false);
555 TfType::_TypeInfo *info = r.FindByPythonClass(classObj.Get());
556
557 return info ? info->canonicalTfType : GetUnknownType();
558 }
559 #endif // PXR_PYTHON_SUPPORT_ENABLED
560
561 const string &
GetTypeName() const562 TfType::GetTypeName() const
563 {
564 return _info->typeName;
565 }
566
567 const std::type_info &
GetTypeid() const568 TfType::GetTypeid() const
569 {
570 std::type_info const *typeInfo = _info->typeInfo;
571 return typeInfo ? *typeInfo : typeid(void);
572 }
573
574 #ifdef PXR_PYTHON_SUPPORT_ENABLED
575 TfPyObjWrapper
GetPythonClass() const576 TfType::GetPythonClass() const
577 {
578 if (!TfPyIsInitialized())
579 TF_CODING_ERROR("Python has not been initialized");
580
581 ScopedLock lock(_info->mutex, /*write=*/false);
582 if (_info->pyClass.get())
583 return TfPyObjWrapper(boost::python::object(_info->pyClass));
584 return TfPyObjWrapper();
585 }
586 #endif // PXR_PYTHON_SUPPORT_ENABLED
587
588 vector<string>
GetAliases(TfType derivedType) const589 TfType::GetAliases(TfType derivedType) const
590 {
591 ScopedLock lock(_info->mutex, /*write=*/false);
592 if (_info->derivedTypeToAliasesMap) {
593 auto i = _info->derivedTypeToAliasesMap->find(derivedType._info);
594 if (i != _info->derivedTypeToAliasesMap->end())
595 return i->second;
596 }
597 return vector<string>();
598 }
599
600 vector<TfType>
GetBaseTypes() const601 TfType::GetBaseTypes() const
602 {
603 ScopedLock lock(_info->mutex, /*write=*/false);
604 return _info->baseTypes;
605 }
606
607 size_t
GetNBaseTypes(TfType * out,size_t maxBases) const608 TfType::GetNBaseTypes(TfType *out, size_t maxBases) const
609 {
610 ScopedLock lock(_info->mutex, /*write=*/false);
611 size_t numBases = _info->baseTypes.size();
612 auto b = _info->baseTypes.begin();
613 auto e = b + std::min<size_t>(maxBases, numBases);
614 std::copy(b, e, out);
615 return numBases;
616 }
617
618 vector<TfType>
GetDirectlyDerivedTypes() const619 TfType::GetDirectlyDerivedTypes() const
620 {
621 ScopedLock lock(_info->mutex, /*write=*/false);
622 return _info->derivedTypes;
623 }
624
625 void
GetAllDerivedTypes(std::set<TfType> * result) const626 TfType::GetAllDerivedTypes(std::set<TfType> *result) const
627 {
628 ScopedLock lock(_info->mutex, /*write=*/false);
629 for (auto derivedType: _info->derivedTypes) {
630 result->insert(derivedType);
631 derivedType.GetAllDerivedTypes(result);
632 }
633 }
634
635 // Helper for resolving ancestor order in the case of multiple inheritance.
636 static bool
_MergeAncestors(vector<TypeVector> * seqs,TypeVector * result)637 _MergeAncestors(vector<TypeVector> *seqs, TypeVector *result)
638 {
639 while(true)
640 {
641 // Find a candidate for the next type.
642 TfType cand;
643
644 // Try the first element of each non-empty sequence, in order.
645 bool anyLeft = false;
646 TF_FOR_ALL(candSeq, *seqs)
647 {
648 if (candSeq->empty())
649 continue;
650
651 anyLeft = true;
652 cand = candSeq->front();
653
654 // Check that the candidate does not occur in the tail
655 // ("cdr", in lisp terms) of any of the sequences.
656 TF_FOR_ALL(checkSeq, *seqs)
657 {
658 if (checkSeq->size() <= 1)
659 continue;
660
661 if (std::find( ++(checkSeq->begin()), checkSeq->end(), cand )
662 != checkSeq->end())
663 {
664 // Reject this candidate.
665 cand = TfType();
666 break;
667 }
668 }
669
670 if (!cand.IsUnknown()) {
671 // Found a candidate
672 break;
673 }
674 }
675
676
677 if (cand.IsUnknown()) {
678 // If we were unable to find a candidate, we're done.
679 // If we've consumed all the inputs, then we've succeeded.
680 // Otherwise, the inheritance hierarchy is inconsistent.
681 return !anyLeft;
682 }
683
684 result->push_back(cand);
685
686 // Remove candidate from input sequences.
687 TF_FOR_ALL(seqIt, *seqs) {
688 if (!seqIt->empty() && seqIt->front() == cand)
689 seqIt->erase( seqIt->begin() );
690 }
691 }
692 }
693
694 void
GetAllAncestorTypes(vector<TfType> * result) const695 TfType::GetAllAncestorTypes(vector<TfType> *result) const
696 {
697 if (IsUnknown()) {
698 TF_CODING_ERROR("Cannot ask for ancestor types of Unknown type");
699 return;
700 }
701
702 const vector<TfType> &baseTypes = GetBaseTypes();
703 const size_t numBaseTypes = baseTypes.size();
704
705 // Simple case: single (or no) inheritance
706 if (numBaseTypes <= 1) {
707 result->push_back(*this);
708 if (numBaseTypes == 1)
709 baseTypes.front().GetAllAncestorTypes(result);
710 return;
711 }
712
713 // Use the C3 algorithm for resolving multiple inheritance;
714 // see motivating comments in header. If this turns out to be a
715 // performance problem, consider memoizing this algorithm.
716
717 vector<TypeVector> seqs;
718 seqs.reserve(2 + numBaseTypes);
719
720 // 1st input sequence: This class.
721 seqs.push_back( TypeVector() );
722 seqs.back().push_back(*this);
723
724 // 2nd input sequence: Direct bases, in order.
725 seqs.push_back( baseTypes );
726
727 // Remaining sequences: Inherited types for each direct base.
728 TF_FOR_ALL(it, baseTypes) {
729 // Populate the base's ancestor types directly into a new vector on
730 // the back of seqs.
731 seqs.push_back( TypeVector() );
732 TypeVector &baseSeq = seqs.back();
733 it->GetAllAncestorTypes(&baseSeq);
734 }
735
736 // Merge the input sequences to resolve final inheritance order.
737 bool ok = _MergeAncestors( &seqs, result );
738
739 if (!ok) {
740 TF_CODING_ERROR("Cannot resolve ancestor classes for '%s' "
741 "because the inheritance hierarchy is "
742 "inconsistent. Please check that multiply-"
743 "inherited types are inherited in the same order "
744 "throughout the inherited hierarchy.",
745 GetTypeName().c_str());
746 }
747 }
748
749 #ifdef PXR_PYTHON_SUPPORT_ENABLED
750 TfType const &
_FindImplPyPolymorphic(PyPolymorphicBase const * ptr)751 TfType::_FindImplPyPolymorphic(PyPolymorphicBase const *ptr) {
752 using namespace boost::python;
753 TfType ret;
754 if (TfPyIsInitialized()) {
755 TfPyLock lock;
756 // See if we can find a polymorphic python object...
757 object pyObj = Tf_FindPythonObject(
758 TfCastToMostDerivedType(ptr), typeid(*ptr));
759 if (!TfPyIsNone(pyObj))
760 ret = FindByPythonClass(
761 TfPyObjWrapper(pyObj.attr("__class__")));
762 }
763 return !ret.IsUnknown() ? ret.GetCanonicalType() : Find(typeid(*ptr));
764 }
765 #endif // PXR_PYTHON_SUPPORT_ENABLED
766
767 bool
_IsAImpl(TfType queryType) const768 TfType::_IsAImpl(TfType queryType) const
769 {
770 // Iterate until we reach more than one parent.
771 for (TfType t = *this; ; ) {
772 if (t == queryType)
773 return true;
774
775 ScopedLock lock(t._info->mutex, /*write=*/false);
776 if (t._info->baseTypes.size() == 1) {
777 t = t._info->baseTypes[0];
778 continue;
779 }
780 for (size_t i = 0; i != t._info->baseTypes.size(); i++)
781 if (t._info->baseTypes[i]._IsAImpl(queryType))
782 return true;
783 return false;
784 }
785 }
786
787 bool
IsA(TfType queryType) const788 TfType::IsA(TfType queryType) const
789 {
790 if (queryType.IsUnknown()) {
791 // If queryType is unknown, it almost always means a previous
792 // type lookup failed, and went unchecked.
793 TF_RUNTIME_ERROR("IsA() was given an Unknown base type. "
794 "This probably means the attempt to look up the "
795 "base type failed. (Note: to explicitly check if a "
796 "type is unknown, use IsUnknown() instead.)");
797 return false;
798 }
799 if (IsUnknown()) {
800 return false;
801 }
802
803 if (*this == queryType || queryType.IsRoot()) {
804 return true;
805 }
806
807 // If the query type doesn't have any child types, then iterating over all
808 // our base types wastes time.
809 ScopedLock queryLock(queryType._info->mutex, /*write=*/false);
810 if (queryType._info->derivedTypes.empty()) {
811 return false;
812 }
813 queryLock.release();
814
815 // printf("--- %s IsA %s { ",
816 // GetTypeName().c_str(), queryType.GetTypeName().c_str());
817
818 bool ret = _IsAImpl(queryType);
819
820 // printf(" } = %d\n", ret);
821
822 return ret;
823 }
824
825 TfType const &
Declare(const string & typeName)826 TfType::Declare(const string &typeName)
827 {
828 TfAutoMallocTag2 tag("Tf", "TfType::Declare");
829
830 TfType t = FindByName(typeName);
831 if (t.IsUnknown()) {
832 auto &r = Tf_TypeRegistry::GetInstance();
833 ScopedLock lock(r.GetMutex(), /*write=*/true);
834 t = TfType(r.NewTypeInfo(typeName));
835 TF_AXIOM(!t._info->IsDefined());
836 }
837 return t.GetCanonicalType();
838 }
839
840 TfType const&
Declare(const string & typeName,const vector<TfType> & newBases,DefinitionCallback definitionCallback)841 TfType::Declare(const string &typeName,
842 const vector<TfType> &newBases,
843 DefinitionCallback definitionCallback)
844 {
845 TfAutoMallocTag2 tag("Tf", "TfType::Declare");
846 TF_DESCRIBE_SCOPE(typeName);
847
848 TfType const& t = Declare(typeName);
849
850 // Check that t does not appear in newBases. This is not comprehensive: t
851 // could be a base of one of the types in newBases, but doing an exhaustive
852 // search is not cheap, and getting it wrong will cause deadlock at
853 // registration time (so it will get noticed and fixed). But this limited
854 // check helps debugging & fixing the most common case of getting this
855 // wrong.
856 auto iter = std::find(newBases.begin(), newBases.end(), t);
857 if (iter != newBases.end()) {
858 TF_FATAL_ERROR("TfType '%s' declares itself as a base.",
859 typeName.c_str());
860 }
861
862 bool sendNotice = false;
863 vector<string> errorsToEmit;
864 {
865 auto &r = Tf_TypeRegistry::GetInstance();
866 ScopedLock regLock(r.GetMutex(), /*write=*/true);
867 ScopedLock typeLock(t._info->mutex, /*write=*/true);
868
869 if (t.IsUnknown() || t.IsRoot()) {
870 errorsToEmit.push_back(
871 TfStringPrintf("Cannot declare the type '%s'",
872 t.GetTypeName().c_str()));
873 goto errorOut;
874 }
875
876 // Update base types.
877 const vector<TfType> &haveBases = t._info->baseTypes;
878
879 // If this type already directly inherits from root, then
880 // prohibit adding any new bases.
881 if (!newBases.empty() &&
882 haveBases.size() == 1 && haveBases.front() == GetRoot()) {
883 errorsToEmit.push_back(
884 TfStringPrintf("Type '%s' has been declared to have 0 bases, "
885 "and therefore inherits directly from the root "
886 "type. Cannot add bases.",
887 t.GetTypeName().c_str()));
888 goto errorOut;
889 }
890
891 if (newBases.empty()) {
892 if (haveBases.empty()) {
893 // If we don't have any bases yet, add the root type.
894 t._AddBases(TypeVector(1, GetRoot()), &errorsToEmit);
895 }
896 } else {
897 // Otherwise, add the new bases.
898 t._AddBases(newBases, &errorsToEmit);
899 }
900
901 if (definitionCallback) {
902 // Prohibit re-declaration of definitionCallback.
903 if (t._info->definitionCallback) {
904 errorsToEmit.push_back(
905 TfStringPrintf("TfType '%s' has already had its "
906 "definitionCallback set; ignoring 2nd "
907 "declaration", typeName.c_str()));
908 goto errorOut;
909 }
910 t._info->definitionCallback = definitionCallback;
911 }
912
913 // Send a notice about this type if we have not done so yet.
914 if (r._sendDeclaredNotification && !t._info->hasSentNotice) {
915 t._info->hasSentNotice = sendNotice = true;
916 }
917 }
918
919 if (sendNotice)
920 TfTypeWasDeclaredNotice(t).Send();
921
922 errorOut:
923
924 // Emit any errors.
925 for (auto const &msg: errorsToEmit)
926 TF_CODING_ERROR(msg);
927
928 return t;
929 }
930
931 #ifdef PXR_PYTHON_SUPPORT_ENABLED
932 void
DefinePythonClass(const TfPyObjWrapper & classObj) const933 TfType::DefinePythonClass(const TfPyObjWrapper & classObj) const
934 {
935 if (IsUnknown() || IsRoot()) {
936 TF_CODING_ERROR("cannot define Python class because type is unknown");
937 return;
938 }
939 auto &r = Tf_TypeRegistry::GetInstance();
940 ScopedLock infoLock(_info->mutex, /*write=*/true);
941 ScopedLock regLock(r.GetMutex(), /*write=*/true);
942 if (!TfPyIsNone(_info->pyClass)) {
943 infoLock.release();
944 regLock.release();
945 TF_CODING_ERROR("TfType '%s' already has a defined Python type; "
946 "cannot redefine", GetTypeName().c_str());
947 return;
948 }
949 r.SetPythonClass(_info, classObj.Get());
950 }
951 #endif // PXR_PYTHON_SUPPORT_ENABLED
952
953 void
_DefineCppType(const std::type_info & typeInfo,size_t sizeofType,bool isPodType,bool isEnumType) const954 TfType::_DefineCppType(const std::type_info & typeInfo,
955 size_t sizeofType, bool isPodType, bool isEnumType) const
956 {
957 auto &r = Tf_TypeRegistry::GetInstance();
958 ScopedLock infoLock(_info->mutex, /*write=*/true);
959 ScopedLock regLock(r.GetMutex(), /*write=*/true);
960 if (_info->typeInfo.load() != nullptr) {
961 infoLock.release();
962 regLock.release();
963 TF_CODING_ERROR("TfType '%s' already has a defined C++ type; "
964 "cannot redefine", GetTypeName().c_str());
965 return;
966 }
967 r.SetTypeInfo(_info, typeInfo, sizeofType, isPodType, isEnumType);
968 }
969
970 void
_AddBases(const TypeVector & newBases,vector<string> * errorsToEmit) const971 TfType::_AddBases(
972 const TypeVector &newBases, vector<string> *errorsToEmit) const
973 {
974 // Callers must hold _info write lock.
975 TypeVector &haveBases = _info->baseTypes;
976
977 // Also we check that all previously-declared bases are included and make
978 // sure that a subsequent registration of base types doesn't change the
979 // order.
980 TypeVector::const_iterator lastNewBaseIter = newBases.begin();
981
982 for(const TfType &haveBase : haveBases) {
983
984 const TypeVector::const_iterator newIter =
985 std::find(newBases.begin(), newBases.end(), haveBase);
986
987 // Repeated base declaration must include all previous bases.
988 if (newIter == newBases.end()) {
989
990 string newBasesStr;
991 for(const TfType &newBase : newBases) {
992 newBasesStr += newBasesStr.empty() ? "" : ", ";
993 newBasesStr += newBase.GetTypeName();
994 }
995
996 errorsToEmit->push_back(TfStringPrintf(
997 "TfType '%s' was previously declared to have '%s' as a base, "
998 "but a subsequent declaration does not include this as a base. "
999 "The newly given bases were: (%s). If this is a type declared "
1000 "in a plugin, check that the plugin metadata is correct.",
1001 GetTypeName().c_str(),
1002 haveBase.GetTypeName().c_str(),
1003 newBasesStr.c_str()));
1004
1005 } else {
1006
1007 // Make sure the new bases are also ordered strictly monotonically
1008 // increasing so that it matches the old order.
1009
1010 if (lastNewBaseIter > newIter) {
1011
1012 std::string haveStr, newStr;
1013 for(const TfType &t : haveBases) {
1014 haveStr += haveStr.empty() ? "" : ", ";
1015 haveStr += t.GetTypeName();
1016 }
1017 for(const TfType &t : newBases) {
1018 newStr += newStr.empty() ? "" : ", ";
1019 newStr += t.GetTypeName();
1020 }
1021 errorsToEmit->push_back(TfStringPrintf(
1022 "Specified base type order differs for %s: had (%s), now "
1023 "(%s). If this is a type declared in a plugin, check that "
1024 "the plugin metadata is correct.",
1025 GetTypeName().c_str(), haveStr.c_str(), newStr.c_str()));
1026 }
1027
1028 lastNewBaseIter = newIter;
1029 }
1030 }
1031
1032 // If we now have more base types, we use the new, longer vector of base
1033 // types to define the order. Note that we don't need to register any
1034 // derived types in that case, because we just ensured we only expanding
1035 // the set of bases.
1036
1037 if (newBases.size() > haveBases.size()) {
1038
1039 for(const TfType &newBase : newBases) {
1040 if (newBase.IsUnknown()) {
1041 errorsToEmit->push_back(
1042 "Specified base type is unknown, skipping.");
1043 continue;
1044 }
1045 if (std::find(haveBases.begin(), haveBases.end(), newBase) ==
1046 haveBases.end()) {
1047
1048 // Tell the new base that it has a new derived type.
1049 ScopedLock baseLock(newBase._info->mutex, /*write=*/true);
1050 newBase._info->derivedTypes.push_back(*this);
1051 }
1052 }
1053
1054 // Fully replace the list of existing bases if needed. This is so that
1055 // we set the order even if we register bases for a type (partially)
1056 // multiple times.
1057 _info->baseTypes = newBases;
1058 }
1059 }
1060
1061 void
_AddCppCastFunc(const std::type_info & baseTypeInfo,_CastFunction func) const1062 TfType::_AddCppCastFunc( const std::type_info & baseTypeInfo,
1063 _CastFunction func ) const
1064 {
1065 ScopedLock infoLock(_info->mutex, /*write=*/true);
1066 _info->SetCastFunc(baseTypeInfo, func);
1067 }
1068
1069 void*
CastToAncestor(TfType ancestor,void * addr) const1070 TfType::CastToAncestor(TfType ancestor, void* addr) const
1071 {
1072 if (IsUnknown() || ancestor.IsUnknown())
1073 return 0;
1074
1075 // Iterate until we reach more than one parent.
1076 for (TfType t = *this; ; ) {
1077 if (t == ancestor)
1078 return addr;
1079 ScopedLock lock(t._info->mutex, /*write=*/false);
1080 if (t._info->baseTypes.size() == 1) {
1081 _CastFunction *castFunc =
1082 t._info->GetCastFunc(t._info->baseTypes[0].GetTypeid());
1083 if (castFunc) {
1084 addr = (*castFunc)(addr, true);
1085 t = t._info->baseTypes[0];
1086 continue;
1087 } else {
1088 return nullptr;
1089 }
1090 }
1091 for (size_t i = 0; i < t._info->baseTypes.size(); i++) {
1092 _CastFunction *castFunc =
1093 t._info->GetCastFunc(t._info->baseTypes[i].GetTypeid());
1094 if (castFunc) {
1095 void *pAddr = (*castFunc)(addr, true);
1096 if (void *final =
1097 t._info->baseTypes[i].CastToAncestor(ancestor, pAddr))
1098 return final;
1099 }
1100 }
1101 return nullptr;
1102 }
1103 }
1104
1105 void*
CastFromAncestor(TfType ancestor,void * addr) const1106 TfType::CastFromAncestor(TfType ancestor, void* addr) const
1107 {
1108 if (IsUnknown() || ancestor.IsUnknown())
1109 return 0;
1110
1111 // No iteration: we have to do the purely recursively, because
1112 // each cast has to happen on the way back *down* the type tree.
1113 if (*this == ancestor)
1114 return addr;
1115
1116 ScopedLock lock(_info->mutex, /*write=*/false);
1117 TF_FOR_ALL(it, _info->baseTypes) {
1118 if (void* tmp = it->CastFromAncestor(ancestor, addr)) {
1119 if (_CastFunction *castFunc = _info->GetCastFunc(it->GetTypeid()))
1120 return (*castFunc)(tmp, false);
1121 }
1122 }
1123
1124 return nullptr;
1125 }
1126
1127 void
SetFactory(std::unique_ptr<FactoryBase> factory) const1128 TfType::SetFactory(std::unique_ptr<FactoryBase> factory) const
1129 {
1130 if (IsUnknown() || IsRoot()) {
1131 TF_CODING_ERROR("Cannot set factory of %s\n",
1132 GetTypeName().c_str());
1133 return;
1134 }
1135
1136 ScopedLock infoLock(_info->mutex, /*write=*/true);
1137 if (_info->factory) {
1138 infoLock.release();
1139 TF_CODING_ERROR("Cannot change the factory of %s\n",
1140 GetTypeName().c_str());
1141 return;
1142 }
1143
1144 _info->factory = std::move(factory);
1145 }
1146
1147 TfType::FactoryBase*
_GetFactory() const1148 TfType::_GetFactory() const
1149 {
1150 if (IsUnknown() || IsRoot()) {
1151 TF_CODING_ERROR("Cannot manufacture type %s", GetTypeName().c_str());
1152 return NULL;
1153 }
1154
1155 _ExecuteDefinitionCallback();
1156
1157 ScopedLock infoLock(_info->mutex, /*write=*/false);
1158 return _info->factory.get();
1159 }
1160
1161 void
_ExecuteDefinitionCallback() const1162 TfType::_ExecuteDefinitionCallback() const
1163 {
1164 // We don't want to call the definition callback while holding the
1165 // registry's lock, so first copy it with the lock held then
1166 // execute it.
1167 ScopedLock infoLock(_info->mutex, /*write=*/false);
1168 if (DefinitionCallback definitionCallback = _info->definitionCallback) {
1169 infoLock.release();
1170 definitionCallback(*this);
1171 }
1172 }
1173
1174 string
GetCanonicalTypeName(const std::type_info & t)1175 TfType::GetCanonicalTypeName(const std::type_info &t)
1176 {
1177 TfAutoMallocTag2 tag("Tf", "TfType::GetCanonicalTypeName");
1178
1179 using LookupMap =
1180 TfHashMap<std::type_index, std::string, std::hash<std::type_index>>;
1181 static LookupMap lookupMap;
1182
1183 static RWMutex mutex;
1184 ScopedLock lock(mutex, /* write = */ false);
1185
1186 const std::type_index typeIndex(t);
1187 const LookupMap &map = lookupMap;
1188 const LookupMap::const_iterator iter = map.find(typeIndex);
1189 if (iter != lookupMap.end()) {
1190 return iter->second;
1191 }
1192
1193 lock.upgrade_to_writer();
1194 return lookupMap.insert({typeIndex, ArchGetDemangled(t)}).first->second;
1195 }
1196
1197 void
AddAlias(TfType base,const string & name) const1198 TfType::AddAlias(TfType base, const string & name) const
1199 {
1200 std::string errMsg;
1201 {
1202 auto &r = Tf_TypeRegistry::GetInstance();
1203 ScopedLock infoLock(base._info->mutex, /*write=*/true);
1204 ScopedLock regLock(r.GetMutex(), /*write=*/true);
1205 // We do not need to hold our own lock here.
1206 r.AddTypeAlias(base._info, this->_info, name, &errMsg);
1207 }
1208
1209 if (!errMsg.empty())
1210 TF_CODING_ERROR(errMsg);
1211 }
1212
1213 bool
IsEnumType() const1214 TfType::IsEnumType() const
1215 {
1216 ScopedLock lock(_info->mutex, /*write=*/false);
1217 return _info->isEnumType;
1218 }
1219
1220 bool
IsPlainOldDataType() const1221 TfType::IsPlainOldDataType() const
1222 {
1223 ScopedLock lock(_info->mutex, /*write=*/false);
1224 return _info->isPodType;
1225 }
1226
1227 size_t
GetSizeof() const1228 TfType::GetSizeof() const
1229 {
1230 ScopedLock lock(_info->mutex, /*write=*/false);
1231 return _info->sizeofType;
1232 }
1233
1234
TF_REGISTRY_FUNCTION(TfType)1235 TF_REGISTRY_FUNCTION(TfType)
1236 {
1237 TfType::Define<void>();
1238 TfType::Define<bool>();
1239 TfType::Define<char>();
1240 TfType::Define<signed char>();
1241 TfType::Define<unsigned char>();
1242 TfType::Define<short>();
1243 TfType::Define<unsigned short>();
1244 TfType::Define<int>();
1245 TfType::Define<unsigned int>();
1246 TfType::Define<long>();
1247 TfType::Define<unsigned long>().AddAlias( TfType::GetRoot(), "size_t" );
1248 TfType::Define<long long>();
1249 TfType::Define<unsigned long long>();
1250 TfType::Define<float>();
1251 TfType::Define<double>();
1252 TfType::Define<string>();
1253
1254 TfType::Define< vector<bool> >()
1255 .AddAlias( TfType::GetRoot(), "vector<bool>" );
1256 TfType::Define< vector<char> >()
1257 .AddAlias( TfType::GetRoot(), "vector<char>" );
1258 TfType::Define< vector<unsigned char> >()
1259 .AddAlias( TfType::GetRoot(), "vector<unsigned char>" );
1260 TfType::Define< vector<short> >()
1261 .AddAlias( TfType::GetRoot(), "vector<short>" );
1262 TfType::Define< vector<unsigned short> >()
1263 .AddAlias( TfType::GetRoot(), "vector<unsigned short>" );
1264 TfType::Define< vector<int> >()
1265 .AddAlias( TfType::GetRoot(), "vector<int>" );
1266 TfType::Define< vector<unsigned int> >()
1267 .AddAlias( TfType::GetRoot(), "vector<unsigned int>" );
1268 TfType::Define< vector<long> >()
1269 .AddAlias( TfType::GetRoot(), "vector<long>" );
1270
1271 TfType ulvec = TfType::Define< vector<unsigned long> >();
1272 ulvec.AddAlias( TfType::GetRoot(), "vector<unsigned long>" );
1273 ulvec.AddAlias( TfType::GetRoot(), "vector<size_t>" );
1274
1275 TfType::Define< vector<long long> >()
1276 .AddAlias( TfType::GetRoot(), "vector<long long>" );
1277 TfType::Define< vector<unsigned long long> >()
1278 .AddAlias( TfType::GetRoot(), "vector<unsigned long long>" );
1279
1280 TfType::Define< vector<float> >()
1281 .AddAlias( TfType::GetRoot(), "vector<float>" );
1282 TfType::Define< vector<double> >()
1283 .AddAlias( TfType::GetRoot(), "vector<double>" );
1284 TfType::Define< vector<string> >()
1285 .AddAlias( TfType::GetRoot(), "vector<string>" );
1286
1287 // Register TfType itself.
1288 TfType::Define<TfType>();
1289 }
1290
1291 std::ostream &
operator <<(std::ostream & out,const TfType & t)1292 operator<<(std::ostream& out, const TfType& t)
1293 {
1294 return out << t.GetTypeName();
1295 }
1296
1297 PXR_NAMESPACE_CLOSE_SCOPE
1298