Allow playback from SdfChangeList.
authorpixar-oss <pixar-oss@users.noreply.github.com>
Sat, 3 Feb 2024 03:25:05 +0000 (19:25 -0800)
committerpixar-oss <pixar-oss@users.noreply.github.com>
Sat, 3 Feb 2024 03:25:05 +0000 (19:25 -0800)
Currently SdfChangeList entries are ambiguous when it comes to
reproducing the edits they indicate.  For example, an entry can
have both didAddNonInertPrim and didRemoveNonInertPrim but it
can't tell you if it was an add followed by a remove or a remove
followed by an add or even add/remove/add, etc.

With this change entries that would be ambiguous when combined are
kept separate.  There is no change to the API.

(Internal change: 2313470)

pxr/usd/sdf/changeList.cpp

index f7ed475affcb1a3efcc980f8dc5e7229c1ee4cd5..9167765f781553d8d4669800fcaea6e355523365 100644 (file)
@@ -177,21 +177,56 @@ SdfChangeList::Entry&
 SdfChangeList::_MoveEntry(SdfPath const &oldPath, SdfPath const &newPath)
 {
     TF_DEV_AXIOM(!oldPath.IsEmpty() && !newPath.IsEmpty());
+    if (oldPath == newPath) {
+        TF_DEV_AXIOM("oldPath and newPath are equal");
+        return _GetEntry(newPath);
+    }
+
+    // Move the old entry to the tmp space then reset, if it exists.
+    // By resetting the old entry we're leaving an empty marker where
+    // in the entry list the object was moved from.  This is needed when
+    // replaying changes to create the prim at the right time so the
+    // prim order is reproduced.  For example, if we create /A then /B
+    // then rename /A to /C we'll get an empty /A entry, a creation /B
+    // entry, and a rename /C entry.  If we didn't keep the /A then
+    // replaying would create /B then /C and the prim order would be
+    // [B, C].  The prim order should be [C, B] since we created A
+    // first.
     Entry tmp;
     auto constIter = FindEntry(oldPath);
     if (constIter != _entries.end()) {
-        // Move the old entry to the tmp space, then erase.
         auto iter = _MakeNonConstIterator(constIter);
-        tmp = std::move(iter->second);
-        // Erase the element and rebuild the accelerator if needed.
-        _entries.erase(iter);
-        _RebuildAccel();
+        std::swap(tmp, iter->second);
+
+        // If the object wasn't created then we don't need to keep it.
+        // If the object was itself due to a rename then we don't need
+        // to keep it.
+        const auto& flags = tmp.flags;
+        if (!(flags.didAddInertPrim ||
+                flags.didAddNonInertPrim ||
+                flags.didAddProperty ||
+                flags.didAddPropertyWithOnlyRequiredFields) ||
+                !tmp.oldPath.IsEmpty()) {
+            _entries.erase(iter);
+            _RebuildAccel();
+        }
     }
+
     // Find or create the new entry, and move tmp over it.  This either
     // populates the new entry with the old entry (if one existed) or it clears
     // out the new entry.
     Entry &newEntry = _GetEntry(newPath);
     newEntry = std::move(tmp);
+
+    // Indicate that a rename occurred.
+    newEntry.flags.didRename = true;
+
+    // Record the source path, but only if it has not already been set
+    // by a prior rename during this round of change processing.
+    if (newEntry.oldPath.IsEmpty()) {
+        newEntry.oldPath = oldPath;
+    }
+
     return newEntry;
 }
 
@@ -224,11 +259,23 @@ SdfChangeList::FindEntry(const SdfPath & path) const
         }
     }
     else {
-        // Linear search the unsorted range.
-        iter = std::find_if(_entries.begin(), _entries.end(),
-                            [&path](EntryList::value_type const &e) {
-                                return e.first == path;
-                            });
+        // Reverse linear search the "unsorted" range.  Entries are added
+        // sequentially so the order mostly reflects the order changes
+        // happened.  We can accumulate changes into an entry that isn't
+        // last in _entries (thus the "mostly" above) but it's always the
+        // last entry for a given path.  By doing a reverse search we get
+        // that last entry.
+        auto riter = std::find_if(_entries.rbegin(), _entries.rend(),
+                                  [&path](EntryList::value_type const &e) {
+                                      return e.first == path;
+                                  });
+        if (riter == _entries.rend()) {
+            iter = _entries.end();
+        }
+        else {
+            // Reverse iterator is off-by-one from normal iterator.
+            iter = std::prev(riter.base());
+        }
     }
     return iter;
 }
@@ -239,7 +286,7 @@ SdfChangeList::_AddNewEntry(SdfPath const &path)
     _entries.emplace_back(std::piecewise_construct,
                           std::tie(path), std::tuple<>());
     if (_entriesAccel) {
-        _entriesAccel->emplace(path, _entries.size()-1);
+        _entriesAccel->insert_or_assign(path, _entries.size()-1);
     }
     else if (ARCH_UNLIKELY(_entries.size() >= _AccelThreshold)) {
         _RebuildAccel();
@@ -255,7 +302,7 @@ SdfChangeList::_RebuildAccel()
                             SdfPath, size_t, SdfPath::Hash>(_entries.size()));
         size_t idx = 0;
         for (auto const &p: _entries) {
-            _entriesAccel->emplace(p.first, idx++);
+            _entriesAccel->insert_or_assign(p.first, idx++);
         }
     }
     else {
@@ -340,34 +387,15 @@ SdfChangeList::DidChangePrimName(const SdfPath & oldPath,
 {
     Entry &newEntry = _GetEntry(newPath);
 
-    if (newEntry.flags.didRemoveNonInertPrim) {
-        // We've already removed a spec at the target, so we can't simply
-        // overwrite the newPath entries with the ones from oldPath.
-        // Nor is it clear how to best merge the edits, while retaining
-        // the didRename hints. Instead, we simply fall back to treating
-        // this case as though newPath and oldPath were both removed,
-        // and a new spec added at newPath.
-        newEntry = Entry();
-        newEntry.flags.didRemoveNonInertPrim = true;
-        newEntry.flags.didAddNonInertPrim = true;
-
-        // Fetch oldEntry -- note that this can invalidate 'newEntry'!
-        Entry &oldEntry = _GetEntry(oldPath);
-        // Clear out existing edits.
-        oldEntry = Entry();
-        oldEntry.flags.didRemoveNonInertPrim = true;
-    } else {
-        // Transfer accumulated changes about oldPath to apply to newPath.
-        Entry &moved = _MoveEntry(oldPath, newPath);
-
-        // Indicate that a rename occurred.
-        moved.flags.didRename = true;
-
-        // Record the source path, but only if it has not already been set
-        // by a prior rename during this round of change processing.
-        if (moved.oldPath.IsEmpty())
-            moved.oldPath = oldPath;
+    // If the prim at newPath was previously removed then create a new
+    // entry for the move so we keep a separate record of the removal.
+    if (newEntry.flags.didRemoveInertPrim ||
+        newEntry.flags.didRemoveNonInertPrim) {
+        _AddNewEntry(newPath);
     }
+
+    // Transfer accumulated changes about oldPath to apply to newPath.
+    _MoveEntry(oldPath, newPath);
 }
 
 void
@@ -403,30 +431,50 @@ SdfChangeList::DidReorderPrims(const SdfPath & parentPath)
 void
 SdfChangeList::DidAddPrim(const SdfPath &path, bool inert)
 {
-    if (inert)
-        _GetEntry(path).flags.didAddInertPrim = true;
-    else
-        _GetEntry(path).flags.didAddNonInertPrim = true;
+    auto* entry = &_GetEntry(path);
+
+    // If this prim was previously removed then create a new entry for the
+    // add so we keep a separate record of the removal.  This avoids a
+    // which-came-first ambiguity when both add and remove flags are set.
+    if (entry->flags.didRemoveInertPrim || entry->flags.didRemoveNonInertPrim) {
+        entry = &_AddNewEntry(path);
+    }
+
+    if (inert) {
+        entry->flags.didAddInertPrim = true;
+    }
+    else {
+        entry->flags.didAddNonInertPrim = true;
+    }
 }
 
 void
 SdfChangeList::DidRemovePrim(const SdfPath &path, bool inert)
 {
-    if (inert)
-        _GetEntry(path).flags.didRemoveInertPrim = true;
-    else
-        _GetEntry(path).flags.didRemoveNonInertPrim = true;
+    auto* entry = &_GetEntry(path);
+
+    // If this prim was previously added then create a new entry for the
+    // remove so we keep a separate record of the addition.  This avoids
+    // a which-came-first ambiguity when both add and remove flags are
+    // set.
+    if (entry->flags.didAddInertPrim || entry->flags.didAddNonInertPrim) {
+        entry = &_AddNewEntry(path);
+    }
+
+    if (inert) {
+        entry->flags.didRemoveInertPrim = true;
+    }
+    else {
+        entry->flags.didRemoveNonInertPrim = true;
+    }
 }
 
 void
 SdfChangeList::DidMovePrim(const SdfPath &oldPath, const SdfPath &newPath)
 {
-    Entry &oldEntry = _GetEntry(oldPath);
-    oldEntry.flags.didRemoveNonInertPrim = true;
-
-    Entry &newEntry = _GetEntry(newPath);
-    newEntry.flags.didAddNonInertPrim = true;
-    newEntry.oldPath = oldPath;
+    DidRemovePrim(oldPath, false);
+    DidAddPrim(newPath, false);
+    _GetEntry(newPath).oldPath = oldPath;
 }
 
 void
@@ -435,34 +483,15 @@ SdfChangeList::DidChangePropertyName(const SdfPath & oldPath,
 {
     Entry &newEntry = _GetEntry(newPath);
 
-    if (newEntry.flags.didRemoveProperty) {
-        // We've already removed a spec at the target, so we can't simply
-        // overrwrite the newPath entries with the ones from oldPath.
-        // Nor is it clear how to best merge the edits, while retaining
-        // the didRename hints. Instead, we simply fall back to treating
-        // this case as though newPath and oldPath were both removed,
-        // and a new spec added at newPath.
-        newEntry = Entry();
-        newEntry.flags.didRemoveProperty = true;
-        newEntry.flags.didAddProperty = true;
-
-        // Note that fetching oldEntry may create its entry and invalidate
-        // 'newEntry'!
-        Entry &oldEntry = _GetEntry(oldPath);
-        oldEntry = Entry();
-        _GetEntry(oldPath).flags.didRemoveProperty = true;
-    } else {
-        // Transfer accumulated changes about oldPath to apply to newPath.
-        Entry &moved = _MoveEntry(oldPath, newPath);
-
-        // Indicate that ac rename occurred.
-        moved.flags.didRename = true;
-
-        // Record the source path, but only if it has not already been set
-        // by a prior rename during this round of change processing.
-        if (moved.oldPath.IsEmpty())
-            moved.oldPath = oldPath;
+    // If the property at newPath was previously removed then create a new
+    // entry for the move so we keep a separate record of the removal.
+    if (newEntry.flags.didRemovePropertyWithOnlyRequiredFields ||
+        newEntry.flags.didRemoveProperty) {
+        _AddNewEntry(newPath);
     }
+
+    // Transfer accumulated changes about oldPath to apply to newPath.
+    _MoveEntry(oldPath, newPath);
 }
 
 void
@@ -474,19 +503,43 @@ SdfChangeList::DidReorderProperties(const SdfPath & parentPath)
 void
 SdfChangeList::DidAddProperty(const SdfPath &path, bool hasOnlyRequiredFields)
 {
-    if (hasOnlyRequiredFields)
-        _GetEntry(path).flags.didAddPropertyWithOnlyRequiredFields = true;
-    else
-        _GetEntry(path).flags.didAddProperty = true;
+    auto* entry = &_GetEntry(path);
+
+    // If this property was previously removed then create a new entry for
+    // the move so we keep a separate record of the addition.  This avoids
+    // a which-came-first ambiguity when both add and remove flags are set.
+    if (entry->flags.didRemovePropertyWithOnlyRequiredFields ||
+        entry->flags.didRemoveProperty) {
+        entry = &_AddNewEntry(path);
+    }
+
+    if (hasOnlyRequiredFields) {
+        entry->flags.didAddPropertyWithOnlyRequiredFields = true;
+    }
+    else {
+        entry->flags.didAddProperty = true;
+    }
 }
 
 void
 SdfChangeList::DidRemoveProperty(const SdfPath &path, bool hasOnlyRequiredFields)
 {
-    if (hasOnlyRequiredFields)
-        _GetEntry(path).flags.didRemovePropertyWithOnlyRequiredFields = true;
-    else
-        _GetEntry(path).flags.didRemoveProperty = true;
+    auto* entry = &_GetEntry(path);
+
+    // If this property was previously added then create a new entry for
+    // the remove so we keep a separate record of the removal.  This avoids
+    // a which-came-first ambiguity when both add and remove flags are set..
+    if (entry->flags.didAddPropertyWithOnlyRequiredFields ||
+        entry->flags.didAddProperty) {
+        entry = &_AddNewEntry(path);
+    }
+
+    if (hasOnlyRequiredFields) {
+        entry->flags.didRemovePropertyWithOnlyRequiredFields = true;
+    }
+    else {
+        entry->flags.didRemoveProperty = true;
+    }
 }
 
 void
@@ -510,13 +563,31 @@ SdfChangeList::DidChangeRelationshipTargets(const SdfPath &relPath)
 void
 SdfChangeList::DidAddTarget(const SdfPath &targetPath)
 {
-    _GetEntry(targetPath).flags.didAddTarget = true;
+    auto* entry = &_GetEntry(targetPath);
+
+    // If this target was previously removed then create a new entry for
+    // the add so we keep a separate record of the addition.  This avoids
+    // a which-came-first ambiguity when both add and remove flags are set.
+    if (entry->flags.didRemoveTarget) {
+        entry = &_AddNewEntry(targetPath);
+    }
+
+    entry->flags.didAddTarget = true;
 }
 
 void
 SdfChangeList::DidRemoveTarget(const SdfPath &targetPath)
 {
-    _GetEntry(targetPath).flags.didRemoveTarget = true;
+    auto* entry = &_GetEntry(targetPath);
+
+    // If this target was previously added then create a new entry for
+    // the remove so we keep a separate record of the removal.  This avoids
+    // a which-came-first ambiguity when both add and remove flags are set.
+    if (entry->flags.didAddTarget) {
+        entry = &_AddNewEntry(targetPath);
+    }
+
+    entry->flags.didRemoveTarget = true;
 }
 
 PXR_NAMESPACE_CLOSE_SCOPE