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 #include "pxr/usd/pcp/mapFunction.h"
27 #include "pxr/base/trace/trace.h"
28 #include "pxr/base/tf/diagnostic.h"
29 #include "pxr/base/tf/enum.h"
30 #include "pxr/base/tf/mallocTag.h"
31 #include "pxr/base/tf/staticData.h"
32 #include "pxr/base/tf/stringUtils.h"
33 #include "pxr/base/tf/ostreamMethods.h"
34 
35 #include <boost/functional/hash.hpp>
36 
37 #include <limits>
38 
39 PXR_NAMESPACE_OPEN_SCOPE
40 
41 namespace {
42     // Order PathPairs using FastLessThan.
43     struct _PathPairOrder
44     {
operator ()__anon23f584260111::_PathPairOrder45         bool operator()(const PcpMapFunction::PathPair &lhs,
46                         const PcpMapFunction::PathPair &rhs) {
47             SdfPath::FastLessThan less;
48             // We need to ensure that "root identity" elements appear first
49             // ('/' -> '/') so we special-case those.
50             SdfPath const &absRoot = SdfPath::AbsoluteRootPath();
51             if (lhs == rhs) {
52                 return false;
53             }
54             if (lhs.first == absRoot && lhs.second == absRoot) {
55                 return true;
56             }
57             if (rhs.first == absRoot && rhs.second == absRoot) {
58                 return false;
59             }
60             return less(lhs.first, rhs.first) ||
61                 (lhs.first == rhs.first && less(lhs.second, rhs.second));
62         }
63     };
64 };
65 
PcpMapFunction(PathPair const * begin,PathPair const * end,SdfLayerOffset offset,bool hasRootIdentity)66 PcpMapFunction::PcpMapFunction(PathPair const *begin,
67                                PathPair const *end,
68                                SdfLayerOffset offset,
69                                bool hasRootIdentity)
70     : _data(begin, end, hasRootIdentity)
71     , _offset(offset)
72 {
73 }
74 
75 // Canonicalize pairs in-place by removing all redundant entries.  Redundant
76 // entries are those which can be removed without changing the semantics of the
77 // correspondence.  Note that this function modifies both the content of `begin`
78 // and `end` as well as the *value* of `begin` and `end` to produce the
79 // resulting range.  Return true if there's a root identity mapping ('/' ->
80 // '/').  It will not appear in the resulting \p vec.
81 template <class PairIter>
82 static bool
_Canonicalize(PairIter & begin,PairIter & end)83 _Canonicalize(PairIter &begin, PairIter &end)
84 {
85     TRACE_FUNCTION();
86 
87     for (PairIter i = begin; i != end; /* increment below */) {
88         bool redundant = false;
89 
90         // Check for trivial dupes before doing further work.
91         for (PairIter j = begin; j != i; ++j) {
92             if (*i == *j) {
93                 redundant = true;
94                 break;
95             }
96         }
97 
98         // Find the closest enclosing mapping.  If the trailing name
99         // components do not match, this pair cannot be redundant.
100         if (!redundant && i->first.GetNameToken() == i->second.GetNameToken()) {
101             // The tail component matches.  Walk up the prefixes.
102             for (SdfPath source = i->first, target = i->second;
103                  !source.IsEmpty() && !target.IsEmpty()
104                  && !redundant;
105                  source = source.GetParentPath(),
106                  target = target.GetParentPath())
107             {
108                 // Check for a redundant mapping.
109                 for (PairIter j = begin; j != end; ++j)
110                 {
111                     // *j makes *i redundant if *j maps source to target.
112                     if (i != j && j->first == source && j->second == target) {
113                         // Found the closest enclosing mapping, *j.
114                         // It means *i is the same as *j plus the addition
115                         // of an identical series of path components on both
116                         // the source and target sides -- which we verified
117                         // as we peeled off trailing path components to get
118                         // here.
119                         redundant = true;
120                         break;
121                     }
122                 }
123                 if (source.GetNameToken() != target.GetNameToken()) {
124                     // The trailing name components do not match,
125                     // so this pair cannot be redundant.
126                     break;
127                 }
128             }
129         }
130 
131         if (redundant) {
132             // Entries are not sorted yet so swap to back for O(1) erase.
133             std::swap(*i, *(end-1));
134             --end;
135         } else {
136             ++i;
137         }
138     }
139 
140     // Final sort to canonical order.
141     std::sort(begin, end, _PathPairOrder());
142 
143     bool hasRootIdentity = false;
144     if (begin != end) {
145         auto const &absroot = SdfPath::AbsoluteRootPath();
146         if (begin->first == absroot && begin->second == absroot) {
147             ++begin;
148             hasRootIdentity = true;
149         }
150     }
151     return hasRootIdentity;
152 }
153 
154 PcpMapFunction
Create(const PathMap & sourceToTarget,const SdfLayerOffset & offset)155 PcpMapFunction::Create(const PathMap &sourceToTarget,
156                        const SdfLayerOffset &offset)
157 {
158     TfAutoMallocTag2 tag("Pcp", "PcpMapFunction");
159     TRACE_FUNCTION();
160 
161     // If we're creating the identity map function, just return it directly.
162     auto absoluteRoot = SdfPath::AbsoluteRootPath();
163     if (sourceToTarget.size() == 1 && offset.IsIdentity()) {
164         auto const &pathPair = *sourceToTarget.begin();
165         if (pathPair.first == absoluteRoot && pathPair.second == absoluteRoot) {
166             return Identity();
167         }
168     }
169 
170     // Validate the arguments.
171     {
172         // Make sure we don't exhaust the representable range.
173         const _Data::PairCount maxPairCount =
174             std::numeric_limits<_Data::PairCount>::max();
175         if (sourceToTarget.size() > maxPairCount) {
176             TF_RUNTIME_ERROR("Cannot construct a PcpMapFunction with %zu "
177                              "entries; limit is %zu",
178                              sourceToTarget.size(), size_t(maxPairCount));
179             return PcpMapFunction();
180         }
181     }
182     TF_FOR_ALL(i, sourceToTarget) {
183         // Source and target paths must be prim paths, because mappings
184         // are used on arcs and arcs are only expressed between prims.
185         //
186         // They also must not contain variant selections.  Variant
187         // selections are purely an aspect of addressing layer opinion
188         // storage.  They are *not* an aspect of composed scene namespace.
189         //
190         // This is a coding error, because a PcpError should have been
191         // emitted about these conditions before getting to this point.
192 
193         const SdfPath & source = i->first;
194         const SdfPath & target = i->second;
195         if (!source.IsAbsolutePath() ||
196             !(source.IsAbsoluteRootOrPrimPath() ||
197               source.IsPrimVariantSelectionPath()) ||
198             !target.IsAbsolutePath() ||
199             !(target.IsAbsoluteRootOrPrimPath() ||
200               target.IsPrimVariantSelectionPath())) {
201             TF_CODING_ERROR("The mapping of '%s' to '%s' is invalid.",
202                             source.GetText(), target.GetText());
203             return PcpMapFunction();
204         }
205     }
206 
207     PathPairVector vec(sourceToTarget.begin(), sourceToTarget.end());
208     PathPair *begin = vec.data(), *end = vec.data() + vec.size();
209     bool hasRootIdentity = _Canonicalize(begin, end);
210     return PcpMapFunction(begin, end, offset, hasRootIdentity);
211 }
212 
213 bool
IsNull() const214 PcpMapFunction::IsNull() const
215 {
216     return _data.IsNull();
217 }
218 
Pcp_MakeIdentity()219 PcpMapFunction *Pcp_MakeIdentity()
220 {
221     PcpMapFunction *ret = new PcpMapFunction;
222     ret->_data.hasRootIdentity = true;
223     return ret;
224 }
225 
226 const PcpMapFunction &
Identity()227 PcpMapFunction::Identity()
228 {
229     static PcpMapFunction *_identityMapFunction = Pcp_MakeIdentity();
230     return *_identityMapFunction;
231 }
232 
TF_MAKE_STATIC_DATA(PcpMapFunction::PathMap,_identityPathMap)233 TF_MAKE_STATIC_DATA(PcpMapFunction::PathMap, _identityPathMap)
234 {
235     const SdfPath & absoluteRootPath = SdfPath::AbsoluteRootPath();
236     _identityPathMap->insert(
237         std::make_pair(absoluteRootPath, absoluteRootPath));
238 }
239 
240 const PcpMapFunction::PathMap &
IdentityPathMap()241 PcpMapFunction::IdentityPathMap()
242 {
243     return *_identityPathMap;
244 }
245 
246 bool
IsIdentity() const247 PcpMapFunction::IsIdentity() const
248 {
249     return *this == Identity();
250 }
251 
252 void
Swap(PcpMapFunction & map)253 PcpMapFunction::Swap(PcpMapFunction& map)
254 {
255     using std::swap;
256     swap(_data, map._data);
257     swap(_offset, map._offset);
258 }
259 
260 bool
operator ==(const PcpMapFunction & map) const261 PcpMapFunction::operator==(const PcpMapFunction &map) const
262 {
263     return _data == map._data && _offset == map._offset;
264 }
265 
266 bool
operator !=(const PcpMapFunction & map) const267 PcpMapFunction::operator!=(const PcpMapFunction &map) const
268 {
269     return !(*this == map);
270 }
271 
272 static SdfPath
_Map(const SdfPath & path,const PcpMapFunction::PathPair * pairs,const int numPairs,bool hasRootIdentity,bool invert)273 _Map(const SdfPath& path,
274      const PcpMapFunction::PathPair *pairs,
275      const int numPairs,
276      bool hasRootIdentity,
277      bool invert)
278 {
279     // Note that we explicitly do not fix target paths here. This
280     // is for consistency, so that consumers can be certain of
281     // PcpMapFunction's behavior. If consumers want target paths
282     // to be fixed, they must be certain to recurse on target paths
283     // themselves.
284     //
285     // XXX: It may be preferable to have PcpMapFunction be in charge
286     //      of doing that, but some path translation issues make that
287     //      infeasible for now.
288 
289     // Find longest prefix that has a mapping;
290     // this represents the most-specific mapping to apply.
291     int bestIndex = -1;
292     size_t bestElemCount = 0;
293     for (int i=0; i < numPairs; ++i) {
294         const SdfPath &source = invert? pairs[i].second : pairs[i].first;
295         const size_t count = source.GetPathElementCount();
296         if (count >= bestElemCount && path.HasPrefix(source)) {
297             bestElemCount = count;
298             bestIndex = i;
299         }
300     }
301     if (bestIndex == -1 && !hasRootIdentity) {
302         // No mapping found.
303         return SdfPath();
304     }
305 
306     SdfPath result;
307     const SdfPath &target = bestIndex == -1 ? SdfPath::AbsoluteRootPath() :
308         invert? pairs[bestIndex].first : pairs[bestIndex].second;
309     if (bestIndex != -1) {
310         const SdfPath &source =
311             invert? pairs[bestIndex].second : pairs[bestIndex].first;
312         result =
313             path.ReplacePrefix(source, target, /* fixTargetPaths = */ false);
314         if (result.IsEmpty()) {
315             return result;
316         }
317     }
318     else {
319         // Use the root identity.
320         result = path;
321     }
322 
323     // To maintain the bijection, we need to check if the mapped path
324     // would translate back to the original path. For instance, given
325     // the mapping:
326     //      { / -> /, /_class_Model -> /Model }
327     //
328     // mapping /Model shouldn't be allowed, as the result is noninvertible:
329     //      source to target: /Model -> /Model (due to identity mapping)
330     //      target to source: /Model -> /_class_Model
331     //
332     // However, given the mapping:
333     //     { /A -> /A/B }
334     //
335     // mapping /A/B should be allowed, as the result is invertible:
336     //     source to target: /A/B -> /A/B/B
337     //     target to source: /A/B/B -> /A/B
338     //
339     // Another example:
340     //    { /A -> /B, /C -> /B/C }
341     //
342     // mapping /A/C should not be allowed, as the result is noninvertible:
343     //    source to target: /A/C -> /B/C
344     //    target to source: /B/C -> /C
345     //
346     // For examples, see test case for bug 74847 and bug 112645 in
347     // testPcpMapFunction.
348     //
349     // XXX: It seems inefficient to have to do this check every time
350     //      we do a path mapping. I think it might be possible to figure
351     //      out the 'disallowed' mappings and mark them in the mapping
352     //      in PcpMapFunction's c'tor. That would let us get rid of this
353     //      code. Figuring out the 'disallowed' mappings might be
354     //      expensive though, possibly O(n^2) where n is the number of
355     //      paths in the mapping.
356     //
357 
358     // Optimistically assume the same mapping will be the best;
359     // we can skip even considering any mapping that is shorter.
360     bestElemCount = target.GetPathElementCount();
361     for (int i=0; i < numPairs; ++i) {
362         if (i == bestIndex) {
363             continue;
364         }
365         const SdfPath &target = invert? pairs[i].first : pairs[i].second;
366         const size_t count = target.GetPathElementCount();
367         if (count > bestElemCount && result.HasPrefix(target)) {
368             // There is a more-specific reverse mapping for this path.
369             return SdfPath();
370         }
371     }
372     return result;
373 }
374 
375 SdfPath
MapSourceToTarget(const SdfPath & path) const376 PcpMapFunction::MapSourceToTarget(const SdfPath & path) const
377 {
378     return _Map(path, _data.begin(), _data.numPairs, _data.hasRootIdentity,
379                 /* invert */ false);
380 }
381 
382 SdfPath
MapTargetToSource(const SdfPath & path) const383 PcpMapFunction::MapTargetToSource(const SdfPath & path) const
384 {
385     return _Map(path, _data.begin(), _data.numPairs, _data.hasRootIdentity,
386                 /* invert */ true);
387 }
388 
389 PcpMapFunction
Compose(const PcpMapFunction & inner) const390 PcpMapFunction::Compose(const PcpMapFunction &inner) const
391 {
392     TfAutoMallocTag2 tag("Pcp", "PcpMapFunction");
393     TRACE_FUNCTION();
394 
395     // Fast path identities.  These do occur in practice and are
396     // worth special-casing since it lets us avoid heap allocation.
397     if (IsIdentity())
398         return inner;
399     if (inner.IsIdentity())
400         return *this;
401 
402     // A 100k random test subset from a production
403     // shot show a mean result size of 1.906050;
404     // typically a root identity + other path pair.
405     constexpr int NumLocalPairs = 4;
406 
407     PathPair localSpace[NumLocalPairs];
408     std::vector<PathPair> remoteSpace;
409     PathPair *scratchBegin = localSpace;
410     int maxRequiredPairs =
411         inner._data.numPairs + int(inner._data.hasRootIdentity) +
412         _data.numPairs + int(_data.hasRootIdentity);
413     if (maxRequiredPairs > NumLocalPairs) {
414         remoteSpace.resize(maxRequiredPairs);
415         scratchBegin = remoteSpace.data();
416     }
417     PathPair *scratch = scratchBegin;
418 
419     // The composition of this function over inner is the result
420     // of first applying inner, then this function.  Build a list
421     // of all of the (source,target) path pairs that result.
422 
423     // Apply outer function to the output range of inner.
424     const _Data& data_inner = inner._data;
425     for (PathPair pair: data_inner) {
426         pair.second = MapSourceToTarget(pair.second);
427         if (!pair.second.IsEmpty()) {
428             if (std::find(scratchBegin, scratch, pair) == scratch) {
429                 *scratch++ = std::move(pair);
430             }
431         }
432     }
433     // If inner has a root identity, map that too.
434     if (inner.HasRootIdentity()) {
435         PathPair pair;
436         pair.first = SdfPath::AbsoluteRootPath();
437         pair.second = MapSourceToTarget(SdfPath::AbsoluteRootPath());
438         if (!pair.second.IsEmpty()) {
439             if (std::find(scratchBegin, scratch, pair) == scratch) {
440                 *scratch++ = std::move(pair);
441             }
442         }
443     }
444 
445 
446     // Apply the inverse of inner to the domain of this function.
447     const _Data& data_outer = _data;
448     for (PathPair pair: data_outer) {
449         pair.first = inner.MapTargetToSource(pair.first);
450         if (!pair.first.IsEmpty()) {
451             if (std::find(scratchBegin, scratch, pair) == scratch) {
452                 *scratch++ = std::move(pair);
453             }
454         }
455     }
456     // If outer has a root identity, map that too.
457     if (HasRootIdentity()) {
458         PathPair pair;
459         pair.first = inner.MapTargetToSource(SdfPath::AbsoluteRootPath());
460         pair.second = SdfPath::AbsoluteRootPath();
461         if (!pair.first.IsEmpty()) {
462             if (std::find(scratchBegin, scratch, pair) == scratch) {
463                 *scratch++ = std::move(pair);
464             }
465         }
466     }
467 
468     bool hasRootIdentity = _Canonicalize(scratchBegin, scratch);
469     return PcpMapFunction(scratchBegin, scratch,
470                           _offset * inner._offset, hasRootIdentity);
471 }
472 
473 PcpMapFunction
ComposeOffset(const SdfLayerOffset & offset) const474 PcpMapFunction::ComposeOffset(const SdfLayerOffset &offset) const
475 {
476     PcpMapFunction composed(*this);
477     composed._offset = composed._offset * offset;
478     return composed;
479 }
480 
481 PcpMapFunction
GetInverse() const482 PcpMapFunction::GetInverse() const
483 {
484     TfAutoMallocTag2 tag("Pcp", "PcpMapFunction");
485 
486     PathPairVector targetToSource;
487     targetToSource.reserve(_data.numPairs);
488     for (PathPair const &pair: _data) {
489         targetToSource.emplace_back(pair.second, pair.first);
490     }
491     PathPair const
492         *begin = targetToSource.data(),
493         *end = targetToSource.data() + targetToSource.size();
494     return PcpMapFunction(
495         begin, end, _offset.GetInverse(), _data.hasRootIdentity);
496 }
497 
498 PcpMapFunction::PathMap
GetSourceToTargetMap() const499 PcpMapFunction::GetSourceToTargetMap() const
500 {
501     PathMap ret(_data.begin(), _data.end());
502     if (_data.hasRootIdentity) {
503         ret[SdfPath::AbsoluteRootPath()] = SdfPath::AbsoluteRootPath();
504     }
505     return ret;
506 }
507 
508 std::string
GetString() const509 PcpMapFunction::GetString() const
510 {
511     std::vector<std::string> lines;
512 
513     if (!GetTimeOffset().IsIdentity()) {
514         lines.push_back(TfStringify(GetTimeOffset()));
515     }
516 
517     PathMap sourceToTargetMap = GetSourceToTargetMap();
518     std::map<SdfPath, SdfPath> sortedMap(sourceToTargetMap.begin(),
519                                          sourceToTargetMap.end());
520     TF_FOR_ALL(it, sortedMap) {
521         lines.push_back(TfStringPrintf("%s -> %s",
522                                        it->first.GetText(),
523                                        it->second.GetText()));
524     }
525 
526     return TfStringJoin(lines.begin(), lines.end(), "\n");
527 }
528 
529 size_t
Hash() const530 PcpMapFunction::Hash() const
531 {
532     size_t hash = _data.hasRootIdentity;
533     boost::hash_combine(hash, _data.numPairs);
534     for (PathPair const &p: _data) {
535         boost::hash_combine(hash, p.first.GetHash());
536         boost::hash_combine(hash, p.second.GetHash());
537     }
538     boost::hash_combine(hash, _offset.GetHash());
539     return hash;
540 }
541 
542 PXR_NAMESPACE_CLOSE_SCOPE
543