Skip to content

Commit

Permalink
Add Tf_TrackingPtr to support custom bookkeeping
Browse files Browse the repository at this point in the history
  • Loading branch information
nvmkuruc committed Jan 2, 2024
1 parent 0127f69 commit 49d6d18
Show file tree
Hide file tree
Showing 3 changed files with 591 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pxr/base/tf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ pxr_library(tf
span.h
staticData.h
staticTokens.h
trackingPtr.h
typeInfoMap.h
type_Impl.h

Expand Down Expand Up @@ -487,6 +488,7 @@ pxr_build_test(testTf
testenv/staticTokens.cpp
testenv/templateString.cpp
testenv/token.cpp
testenv/trackingPtr.cpp
testenv/type.cpp
testenv/typeMultipleInheritance.cpp
testenv/typeInfoMap.cpp
Expand Down Expand Up @@ -749,6 +751,9 @@ pxr_register_test(testTfTemplateString
pxr_register_test(TfToken
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testTf TfToken"
)
pxr_register_test(TfTrackingPtr
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testTf TfTrackingPtr"
)
pxr_register_test(TfType
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testTf TfType"
)
Expand Down
251 changes: 251 additions & 0 deletions pxr/base/tf/testenv/trackingPtr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
//
// Copyright 2023 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//

#include "pxr/pxr.h"
#include "pxr/base/tf/diagnosticLite.h"
#include "pxr/base/tf/regTest.h"
#include "pxr/base/tf/trackingPtr.h"


PXR_NAMESPACE_USING_DIRECTIVE

// Test class which stores its own reference count.
struct RefCountedValue {
int value{5};
mutable int count{0};
RefCountedValue(int v, int c) : value{v}, count{c} {}
};

// Bookkeeper for `RefCountedValue`
struct RefCountedBookkeeper {
void Retain(const RefCountedValue* ptr) {
++(ptr->count);
}
void Release(const RefCountedValue* ptr) {
if (--(ptr->count); ptr->count == 0) {
delete ptr;
}
}
};

using TfTestTrackingPtr =
Tf_TrackingPtr<RefCountedValue, RefCountedBookkeeper>;

using TfTestTrackingConstPtr =
Tf_TrackingPtr<const RefCountedValue, RefCountedBookkeeper>;

// Create a TfTestTrackingPtr.
TfTestTrackingPtr
TfMakeTestTrackingPtr(int value) {
return Tf_MakeTrackingPtr<RefCountedValue, RefCountedBookkeeper>(value, 0);
}

static bool
TestDefault()
{
TF_AXIOM(TfTestTrackingPtr{}.get() == nullptr);
TF_AXIOM(!TfTestTrackingPtr{});
return true;
}

static bool
TestMoveSemantics()
{
// RefCountedValue maintains its own reference count. By defaulting it to
// two, it should never get freed by its bookkeeper and the stack will be
// responsible for releasing memory.
RefCountedValue overcounted{7, 2};
{
TfTestTrackingPtr adopted{TfTrackingPtrDoNotRetainTag, &overcounted};
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(adopted.get() == std::addressof(overcounted));
{
// Exercise moving back and forth using both constructor
// and assignment operator
TfTestTrackingPtr moved{std::move(adopted)};
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(moved.get() == std::addressof(overcounted));
TF_AXIOM(adopted.get() == nullptr);

adopted = std::move(moved);
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(adopted.get() == std::addressof(overcounted));
TF_AXIOM(moved.get() == nullptr);
}
{
// Test self assignment for moving
adopted = std::move(adopted);
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(adopted.get() == std::addressof(overcounted));
}
}
TF_AXIOM(overcounted.count == 1);
return true;
}

static bool
TestCopying()
{
// RefCountedValue maintains its own reference count. By defaulting it to
// two, it should never get freed by its bookkeeper and the stack can
// release its memory.
RefCountedValue overcounted{7, 2};
{
TfTestTrackingPtr adopted{TfTrackingPtrDoNotRetainTag, &overcounted};
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(adopted.get() == std::addressof(overcounted));
{
// Exercise copy assignment
TfTestTrackingPtr copied = adopted;
TF_AXIOM(overcounted.count == 3);
TF_AXIOM(copied.get() == std::addressof(overcounted));
TF_AXIOM(adopted.get() == std::addressof(overcounted));
}
{
// Exercise copy constructor
TfTestTrackingPtr copied(adopted);
TF_AXIOM(overcounted.count == 3);
TF_AXIOM(copied.get() == std::addressof(overcounted));
TF_AXIOM(adopted.get() == std::addressof(overcounted));
}
{
// Test self assignment for copying
adopted = adopted;
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(adopted.get() == std::addressof(overcounted));
}

}
TF_AXIOM(overcounted.count == 1);
return true;
}

static bool
TestRetainTag()
{
RefCountedValue overcounted{10, 1};
{
TF_AXIOM(overcounted.count == 1);
TfTestTrackingPtr adopted(TfTrackingPtrRetainTag, &overcounted);
TF_AXIOM(overcounted.count == 2);
TF_AXIOM(adopted.get() == std::addressof(overcounted));
}
TF_AXIOM(overcounted.count == 1);
return true;
}

static bool
TestMake()
{
auto made = TfMakeTestTrackingPtr(12);
TF_AXIOM(made);
TF_AXIOM(made->count == 1);
return true;
}

static bool
TestPointerOperators()
{
auto made = TfMakeTestTrackingPtr(15);
TF_AXIOM(made.get()->value == 15);
TF_AXIOM(made->value == 15);
TF_AXIOM((*made).value == 15);
return true;
}

static bool
TestNullAssignment()
{
auto made = TfMakeTestTrackingPtr(12);
TF_AXIOM(made->count == 1);
auto copy = made;
TF_AXIOM(made->count == 2);
copy = nullptr;
TF_AXIOM(!copy);
TF_AXIOM(made);
TF_AXIOM(made->count == 1);
return true;
}

static bool
TestEquality()
{
auto made = TfMakeTestTrackingPtr(12);
// Value equivalance doesn't imply address equivalence
auto another = TfMakeTestTrackingPtr(12);
auto copy = made;
TF_AXIOM(copy == made);
TF_AXIOM(another != made);
TF_AXIOM(another != copy);
return true;
}

static bool
TestSwap()
{
auto made = TfMakeTestTrackingPtr(16);
auto copy = made;
auto another = TfMakeTestTrackingPtr(12);
TF_AXIOM(made->count == 2);
TF_AXIOM(made->value == 16);
TF_AXIOM(another->count == 1);
TF_AXIOM(another->value == 12);
made.swap(another);
TF_AXIOM(copy == another);
TF_AXIOM(copy != made);
TF_AXIOM(another->count == 2);
TF_AXIOM(another->value == 16);
TF_AXIOM(made->count == 1);
TF_AXIOM(made->value == 12);
return true;
}

static bool
TestConstConversion()
{
TfTestTrackingPtr made = TfMakeTestTrackingPtr(20);
TfTestTrackingConstPtr constCopy{made};
TfTestTrackingConstPtr anotherConstCopy;
anotherConstCopy = made;
TF_AXIOM(made == constCopy);
TF_AXIOM(made == anotherConstCopy);
TF_AXIOM(made->count == 3);
return true;
}

static bool
Test_TfTrackingPtr() {
return TestDefault() &&
TestCopying() &&
TestMoveSemantics() &&
TestRetainTag() &&
TestMake() &&
TestPointerOperators() &&
TestNullAssignment() &&
TestEquality() &&
TestSwap() &&
TestConstConversion();
}

TF_ADD_REGTEST(TfTrackingPtr);
Loading

0 comments on commit 49d6d18

Please sign in to comment.