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