From a6e68c7e70a175605fa0779add068143c7bc8ed2 Mon Sep 17 00:00:00 2001 From: Pasukhin Dmitry Date: Tue, 25 Nov 2025 16:07:49 +0000 Subject: [PATCH] Foundation Classes - TopLoc package update (#849) - Performance optimizations including binary exponentiation for `Powered()`, optimized hash code computation, and fast-path optimizations for common operations - Code modernization with `noexcept` qualifiers, `constexpr` for compile-time constants, and inline wrapper methods --- .../TKMath/GTests/FILES.cmake | 1 + .../TKMath/GTests/TopLoc_Location_Test.cxx | 722 ++++++++++++++++++ .../TKMath/TopLoc/FILES.cmake | 1 - .../TKMath/TopLoc/TopLoc_Datum3D.hxx | 6 +- .../TKMath/TopLoc/TopLoc_ItemLocation.cxx | 2 +- .../TKMath/TopLoc/TopLoc_Location.cxx | 84 +- .../TKMath/TopLoc/TopLoc_Location.hxx | 29 +- .../TKMath/TopLoc/TopLoc_Location.lxx | 25 +- .../TopLoc/TopLoc_SListNodeOfItemLocation.hxx | 21 +- .../TopLoc/TopLoc_SListNodeOfItemLocation.lxx | 33 - .../TopLoc/TopLoc_SListOfItemLocation.cxx | 17 +- .../TopLoc/TopLoc_SListOfItemLocation.hxx | 16 +- .../TKernel/GTests/FILES.cmake | 1 - .../TKernel/GTests/TopLoc_Location_Test.cxx | 91 --- 14 files changed, 837 insertions(+), 212 deletions(-) create mode 100644 src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx delete mode 100644 src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx delete mode 100644 src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx diff --git a/src/FoundationClasses/TKMath/GTests/FILES.cmake b/src/FoundationClasses/TKMath/GTests/FILES.cmake index 615955d3ad..7f3afa417b 100644 --- a/src/FoundationClasses/TKMath/GTests/FILES.cmake +++ b/src/FoundationClasses/TKMath/GTests/FILES.cmake @@ -45,4 +45,5 @@ set(OCCT_TKMath_GTests_FILES PLib_Test.cxx PLib_JacobiPolynomial_Test.cxx PLib_HermitJacobi_Test.cxx + TopLoc_Location_Test.cxx ) diff --git a/src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx b/src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx new file mode 100644 index 0000000000..8cf4fa9f79 --- /dev/null +++ b/src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx @@ -0,0 +1,722 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace +{ +//! Functor for testing concurrent access to TopLoc_Location::Transformation() +struct TopLocTransformFunctor +{ + TopLocTransformFunctor(const std::vector& theShapeVec) + : myShapeVec(&theShapeVec), + myIsRaceDetected(0) + { + } + + void operator()(size_t i) const + { + if (!myIsRaceDetected) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(myShapeVec->at(i)); + gp_Pnt aPoint = BRep_Tool::Pnt(aVertex); + if (aPoint.X() != static_cast(i)) + { + ++myIsRaceDetected; + } + } + } + + const std::vector* myShapeVec; + mutable std::atomic myIsRaceDetected; +}; +} // namespace + +TEST(TopLoc_Location_Test, OCC25545_ConcurrentTransformationAccess) +{ + // Bug OCC25545: TopLoc_Location::Transformation() provokes data races + // This test verifies that concurrent access to TopLoc_Location::Transformation() + // does not cause data races or incorrect geometry results + + // Place vertices in a vector, giving the i-th vertex the + // transformation that translates it on the vector (i,0,0) from the origin + Standard_Integer n = 1000; + std::vector aShapeVec(n); + std::vector aLocVec(n); + TopoDS_Shape aShape = BRepBuilderAPI_MakeVertex(gp::Origin()); + aShapeVec[0] = aShape; + + for (Standard_Integer i = 1; i < n; ++i) + { + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1, 0, 0)); + aLocVec[i] = aLocVec[i - 1] * aTrsf; + aShapeVec[i] = aShape.Moved(aLocVec[i]); + } + + // Evaluator function will access vertices geometry concurrently + TopLocTransformFunctor aFunc(aShapeVec); + + // Process concurrently + OSD_Parallel::For(0, n, aFunc); + + // Verify no data race was detected + EXPECT_EQ(aFunc.myIsRaceDetected, 0) + << "Data race detected in concurrent TopLoc_Location::Transformation() access"; +} + +//================================================================================================= +// Tests for Phase 1 & 2 Optimizations +//================================================================================================= + +TEST(TopLoc_Location_Test, DefaultConstructor_CreatesIdentity) +{ + // Test that default constructor creates identity location + TopLoc_Location aLoc; + + EXPECT_TRUE(aLoc.IsIdentity()) << "Default constructed location should be identity"; + + const gp_Trsf& aTrsf = aLoc.Transformation(); + EXPECT_EQ(aTrsf.Form(), gp_Identity) << "Default location should return identity transformation"; +} + +TEST(TopLoc_Location_Test, IdentityTransformation_ReturnsSameInstance) +{ + // Test that identity locations return the same transformation instance + TopLoc_Location aLoc1; + TopLoc_Location aLoc2; + + const gp_Trsf& aTrsf1 = aLoc1.Transformation(); + const gp_Trsf& aTrsf2 = aLoc2.Transformation(); + + // Both should return reference to the same identity transformation + EXPECT_EQ(&aTrsf1, &aTrsf2) << "Identity locations should share the same transformation instance"; +} + +TEST(TopLoc_Location_Test, Squared_EqualsMultipliedBySelf) +{ + // Test that Squared() equals Multiplied(self) + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); + TopLoc_Location aLoc(aTrsf); + + TopLoc_Location aSquared = aLoc.Squared(); + TopLoc_Location aMultiplied = aLoc.Multiplied(aLoc); + + EXPECT_EQ(aSquared, aMultiplied) << "Squared() should equal Multiplied(self)"; + + // Verify the transformation is actually squared + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aTransformed = anOrigin.Transformed(aSquared.Transformation()); + gp_Pnt anExpected(2.0, 4.0, 6.0); // Double translation + + EXPECT_NEAR(aTransformed.X(), anExpected.X(), 1e-10); + EXPECT_NEAR(aTransformed.Y(), anExpected.Y(), 1e-10); + EXPECT_NEAR(aTransformed.Z(), anExpected.Z(), 1e-10); +} + +TEST(TopLoc_Location_Test, Powered2_UsesSquaredFastPath) +{ + // Test that Powered(2) equals Squared() (verifies fast path) + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(5.0, -3.0, 1.5)); + TopLoc_Location aLoc(aTrsf); + + TopLoc_Location aPowered = aLoc.Powered(2); + TopLoc_Location aSquared = aLoc.Squared(); + + EXPECT_EQ(aPowered, aSquared) << "Powered(2) should equal Squared()"; +} + +TEST(TopLoc_Location_Test, Powered_VariousPowers) +{ + // Test Powered() with various power values + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + // Power 0 should return identity + TopLoc_Location aPow0 = aLoc.Powered(0); + EXPECT_TRUE(aPow0.IsIdentity()) << "Powered(0) should return identity"; + + // Power 1 should return self + TopLoc_Location aPow1 = aLoc.Powered(1); + EXPECT_EQ(aPow1, aLoc) << "Powered(1) should return self"; + + // Power 2 should be double translation + TopLoc_Location aPow2 = aLoc.Powered(2); + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aPow2.Transformation()); + EXPECT_NEAR(aResult.X(), 2.0, 1e-10) << "Powered(2) should double the translation"; + + // Power 3 + TopLoc_Location aPow3 = aLoc.Powered(3); + aResult = anOrigin.Transformed(aPow3.Transformation()); + EXPECT_NEAR(aResult.X(), 3.0, 1e-10) << "Powered(3) should triple the translation"; + + // Negative power (inverse) + TopLoc_Location aPowNeg1 = aLoc.Powered(-1); + TopLoc_Location anInverted = aLoc.Inverted(); + EXPECT_EQ(aPowNeg1, anInverted) << "Powered(-1) should equal Inverted()"; +} + +TEST(TopLoc_Location_Test, SharesNode_DetectsSharedStructure) +{ + // Test that SharesNode correctly detects shared list nodes + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); + TopLoc_Location aLoc1(aTrsf); + + // Copy constructor should share the same nodes + TopLoc_Location aLoc2 = aLoc1; + + EXPECT_EQ(aLoc1, aLoc2) << "Copied location should equal original"; + + // Assignment should also share nodes + TopLoc_Location aLoc3; + aLoc3 = aLoc1; + + EXPECT_EQ(aLoc3, aLoc1) << "Assigned location should equal original"; +} + +TEST(TopLoc_Location_Test, Equality_DifferentTransformations) +{ + // Test that different transformations are not equal + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc1(aTrsf1); + + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(0.0, 1.0, 0.0)); + TopLoc_Location aLoc2(aTrsf2); + + EXPECT_NE(aLoc1, aLoc2) << "Different transformations should not be equal"; + EXPECT_TRUE(aLoc1.IsDifferent(aLoc2)) + << "IsDifferent should return true for different transformations"; +} + +TEST(TopLoc_Location_Test, Multiplication_Composition) +{ + // Test location multiplication (composition) + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc1(aTrsf1); + + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(0.0, 2.0, 0.0)); + TopLoc_Location aLoc2(aTrsf2); + + TopLoc_Location aComposed = aLoc1 * aLoc2; + + // Apply composed transformation + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aComposed.Transformation()); + + EXPECT_NEAR(aResult.X(), 1.0, 1e-10); + EXPECT_NEAR(aResult.Y(), 2.0, 1e-10); + EXPECT_NEAR(aResult.Z(), 0.0, 1e-10); +} + +TEST(TopLoc_Location_Test, Inverted_ProducesInverse) +{ + // Test that Location * Inverted() = Identity + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(5.0, -3.0, 2.0)); + TopLoc_Location aLoc(aTrsf); + + TopLoc_Location anInv = aLoc.Inverted(); + TopLoc_Location anIdentity = aLoc * anInv; + + EXPECT_TRUE(anIdentity.IsIdentity()) << "Location * Inverted() should produce identity"; + + // Also test in reverse order + TopLoc_Location anIdentity2 = anInv * aLoc; + EXPECT_TRUE(anIdentity2.IsIdentity()) << "Inverted() * Location should also produce identity"; +} + +TEST(TopLoc_Location_Test, Divided_EqualsMultipliedByInverse) +{ + // Test that Divided equals Multiplied by inverse + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(3.0, 4.0, 5.0)); + TopLoc_Location aLoc1(aTrsf1); + + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(1.0, 1.0, 1.0)); + TopLoc_Location aLoc2(aTrsf2); + + TopLoc_Location aDivided = aLoc1 / aLoc2; + TopLoc_Location aMultiplied = aLoc1 * aLoc2.Inverted(); + + EXPECT_EQ(aDivided, aMultiplied) << "Divided should equal Multiplied by Inverted"; +} + +TEST(TopLoc_Location_Test, Predivided_EqualsInverseMultiplied) +{ + // Test Predivided = Other.Inverted() * this + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(2.0, 3.0, 4.0)); + TopLoc_Location aLoc1(aTrsf1); + + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(1.0, 1.0, 1.0)); + TopLoc_Location aLoc2(aTrsf2); + + TopLoc_Location aPredivided = aLoc1.Predivided(aLoc2); + TopLoc_Location anExpected = aLoc2.Inverted() * aLoc1; + + EXPECT_EQ(aPredivided, anExpected) << "Predivided should equal Other.Inverted() * this"; +} + +TEST(TopLoc_Location_Test, HashCode_ConsistentForEqualLocations) +{ + // Test that locations sharing the same structure have the same hash code + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); + TopLoc_Location aLoc1(aTrsf); + + // Copy constructor shares the same underlying structure + TopLoc_Location aLoc2 = aLoc1; + + EXPECT_EQ(aLoc1.HashCode(), aLoc2.HashCode()) + << "Copied locations (sharing structure) should have the same hash code"; + + // Identity locations should have the same hash code + TopLoc_Location anId1; + TopLoc_Location anId2; + EXPECT_EQ(anId1.HashCode(), anId2.HashCode()) + << "Identity locations should have the same hash code"; + EXPECT_EQ(anId1.HashCode(), static_cast(0)) << "Identity location hash should be 0"; +} + +TEST(TopLoc_Location_Test, HashCode_DifferentForDifferentLocations) +{ + // Test that different locations (likely) have different hash codes + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc1(aTrsf1); + + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(0.0, 1.0, 0.0)); + TopLoc_Location aLoc2(aTrsf2); + + // Hash codes should (very likely) be different + // Note: Hash collision is theoretically possible but highly unlikely + EXPECT_NE(aLoc1.HashCode(), aLoc2.HashCode()) + << "Different locations should (likely) have different hash codes"; +} + +TEST(TopLoc_Location_Test, ConstCorrectness_TransformationAccess) +{ + // Test const correctness of Transformation() access + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); + const TopLoc_Location aLoc(aTrsf); + + // This should compile - Transformation() is const + const gp_Trsf& aTrsfRef = aLoc.Transformation(); + + EXPECT_EQ(aTrsfRef.Form(), gp_Translation); + + // Test identity case + const TopLoc_Location anIdLoc; + const gp_Trsf& anIdTrsf = anIdLoc.Transformation(); + + EXPECT_EQ(anIdTrsf.Form(), gp_Identity); +} + +TEST(TopLoc_Location_Test, Rotation_Composition) +{ + // Test rotation transformations + gp_Trsf aRot; + gp_Ax1 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)); + aRot.SetRotation(anAxis, M_PI / 2.0); // 90 degrees around Z + + TopLoc_Location aLoc(aRot); + TopLoc_Location aSquared = aLoc.Squared(); + + // Point at (1,0,0) rotated 90 degrees twice should be at (-1,0,0) + gp_Pnt aPnt(1, 0, 0); + gp_Pnt aResult = aPnt.Transformed(aSquared.Transformation()); + + EXPECT_NEAR(aResult.X(), -1.0, 1e-10); + EXPECT_NEAR(aResult.Y(), 0.0, 1e-10); + EXPECT_NEAR(aResult.Z(), 0.0, 1e-10); +} + +TEST(TopLoc_Location_Test, Clear_ResetsToIdentity) +{ + // Test that Clear() resets location to identity + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); + TopLoc_Location aLoc(aTrsf); + + EXPECT_FALSE(aLoc.IsIdentity()) << "Location should not be identity before Clear()"; + + aLoc.Clear(); + + EXPECT_TRUE(aLoc.IsIdentity()) << "Location should be identity after Clear()"; +} + +TEST(TopLoc_Location_Test, Identity_Method) +{ + // Test Identity() method + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); + TopLoc_Location aLoc(aTrsf); + + EXPECT_FALSE(aLoc.IsIdentity()); + + aLoc.Identity(); + + EXPECT_TRUE(aLoc.IsIdentity()) << "Identity() should reset location to identity"; +} + +//================================================================================================= +// Additional Edge Case Tests +//================================================================================================= + +TEST(TopLoc_Location_Test, ChainedMultiplication_MultipleLocations) +{ + // Test multiple location multiplications + gp_Trsf aTrsf1, aTrsf2, aTrsf3; + aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + aTrsf2.SetTranslation(gp_Vec(0.0, 1.0, 0.0)); + aTrsf3.SetTranslation(gp_Vec(0.0, 0.0, 1.0)); + + TopLoc_Location aLoc1(aTrsf1); + TopLoc_Location aLoc2(aTrsf2); + TopLoc_Location aLoc3(aTrsf3); + + // Chain multiplication + TopLoc_Location aResult = aLoc1 * aLoc2 * aLoc3; + + // Apply to point + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aTransformed = anOrigin.Transformed(aResult.Transformation()); + + EXPECT_NEAR(aTransformed.X(), 1.0, 1e-10); + EXPECT_NEAR(aTransformed.Y(), 1.0, 1e-10); + EXPECT_NEAR(aTransformed.Z(), 1.0, 1e-10); +} + +TEST(TopLoc_Location_Test, IdentityMultiplication_NeutralElement) +{ + // Test that identity is neutral element for multiplication + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(5.0, 3.0, 1.0)); + TopLoc_Location aLoc(aTrsf); + TopLoc_Location anIdentity; + + // Identity * Location = Location + TopLoc_Location aResult1 = anIdentity * aLoc; + EXPECT_EQ(aResult1, aLoc) << "Identity * Location should equal Location"; + + // Location * Identity = Location + TopLoc_Location aResult2 = aLoc * anIdentity; + EXPECT_EQ(aResult2, aLoc) << "Location * Identity should equal Location"; +} + +TEST(TopLoc_Location_Test, CopySemantics) +{ + // Test copy construction and assignment + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(2.0, 3.0, 4.0)); + TopLoc_Location anOriginal(aTrsf); + + // Copy construction + TopLoc_Location aCopied(anOriginal); + EXPECT_EQ(aCopied, anOriginal); + EXPECT_EQ(aCopied.HashCode(), anOriginal.HashCode()); + + // Copy assignment + TopLoc_Location anAssigned; + anAssigned = anOriginal; + EXPECT_EQ(anAssigned, anOriginal); + + // All three should be equal (share structure) + EXPECT_EQ(aCopied, anAssigned); +} + +TEST(TopLoc_Location_Test, ScaleTransformation_Composition) +{ + // Test scale transformations + gp_Trsf aScale; + aScale.SetScale(gp_Pnt(0, 0, 0), 2.0); + + TopLoc_Location aLoc(aScale); + TopLoc_Location aSquared = aLoc.Squared(); + + // Point scaled by 2, then by 2 again = scaled by 4 + gp_Pnt aPnt(1, 1, 1); + gp_Pnt aResult = aPnt.Transformed(aSquared.Transformation()); + + EXPECT_NEAR(aResult.X(), 4.0, 1e-10); + EXPECT_NEAR(aResult.Y(), 4.0, 1e-10); + EXPECT_NEAR(aResult.Z(), 4.0, 1e-10); +} + +TEST(TopLoc_Location_Test, ComplexTransformation_TranslationAndRotation) +{ + // Test combination of translation and rotation + gp_Trsf aTrans; + aTrans.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); + + gp_Trsf aRot; + gp_Ax1 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)); + aRot.SetRotation(anAxis, M_PI); // 180 degrees + + TopLoc_Location aLocTrans(aTrans); + TopLoc_Location aLocRot(aRot); + + // Translate then rotate + TopLoc_Location aComposed = aLocRot * aLocTrans; + + // Point at origin, translate to (10,0,0), then rotate 180 degrees -> (-10,0,0) + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aComposed.Transformation()); + + EXPECT_NEAR(aResult.X(), -10.0, 1e-9); + EXPECT_NEAR(aResult.Y(), 0.0, 1e-9); + EXPECT_NEAR(aResult.Z(), 0.0, 1e-9); +} + +TEST(TopLoc_Location_Test, PoweredWithLargePower) +{ + // Test Powered() with larger power value + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + // Power of 10 + TopLoc_Location aPow10 = aLoc.Powered(10); + + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aPow10.Transformation()); + + EXPECT_NEAR(aResult.X(), 10.0, 1e-9) << "Powered(10) should translate 10 units"; +} + +TEST(TopLoc_Location_Test, SelfMultiplication_MultipleIterations) +{ + // Test repeated self-multiplication + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + TopLoc_Location aResult = aLoc; + for (int i = 0; i < 5; ++i) + { + aResult = aResult * aLoc; + } + + // Should be 6 times the original (1 + 5 multiplications) + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aTransformed = anOrigin.Transformed(aResult.Transformation()); + + EXPECT_NEAR(aTransformed.X(), 6.0, 1e-10); +} + +TEST(TopLoc_Location_Test, AssociativityOfMultiplication) +{ + // Test that (A * B) * C = A * (B * C) + gp_Trsf aTrsf1, aTrsf2, aTrsf3; + aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + aTrsf2.SetTranslation(gp_Vec(0.0, 2.0, 0.0)); + aTrsf3.SetTranslation(gp_Vec(0.0, 0.0, 3.0)); + + TopLoc_Location aLocA(aTrsf1); + TopLoc_Location aLocB(aTrsf2); + TopLoc_Location aLocC(aTrsf3); + + TopLoc_Location aLeft = (aLocA * aLocB) * aLocC; + TopLoc_Location aRight = aLocA * (aLocB * aLocC); + + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResultLeft = anOrigin.Transformed(aLeft.Transformation()); + gp_Pnt aResultRight = anOrigin.Transformed(aRight.Transformation()); + + EXPECT_NEAR(aResultLeft.X(), aResultRight.X(), 1e-10); + EXPECT_NEAR(aResultLeft.Y(), aResultRight.Y(), 1e-10); + EXPECT_NEAR(aResultLeft.Z(), aResultRight.Z(), 1e-10); +} + +TEST(TopLoc_Location_Test, InversionIdempotence) +{ + // Test that (L.Inverted()).Inverted() = L + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(3.0, 4.0, 5.0)); + TopLoc_Location aLoc(aTrsf); + + TopLoc_Location aDoubleInverted = aLoc.Inverted().Inverted(); + + gp_Pnt aPnt(1, 1, 1); + gp_Pnt aResult1 = aPnt.Transformed(aLoc.Transformation()); + gp_Pnt aResult2 = aPnt.Transformed(aDoubleInverted.Transformation()); + + EXPECT_NEAR(aResult1.X(), aResult2.X(), 1e-10); + EXPECT_NEAR(aResult1.Y(), aResult2.Y(), 1e-10); + EXPECT_NEAR(aResult1.Z(), aResult2.Z(), 1e-10); +} + +//================================================================================================= +// Tests for Phase 3: Binary Exponentiation Optimization +//================================================================================================= + +TEST(TopLoc_Location_Test, BinaryExponentiation_PowerOf2) +{ + // Test binary exponentiation for powers of 2 (4, 8, 16) + // These should be most efficient: L^8 requires only 3 squarings instead of 7 multiplications + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + // Test power of 4 (2^2) + TopLoc_Location aPow4 = aLoc.Powered(4); + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aPow4.Transformation()); + EXPECT_NEAR(aResult.X(), 4.0, 1e-9) << "Powered(4) should translate 4 units"; + + // Test power of 8 (2^3) + TopLoc_Location aPow8 = aLoc.Powered(8); + aResult = anOrigin.Transformed(aPow8.Transformation()); + EXPECT_NEAR(aResult.X(), 8.0, 1e-9) << "Powered(8) should translate 8 units"; + + // Test power of 16 (2^4) + TopLoc_Location aPow16 = aLoc.Powered(16); + aResult = anOrigin.Transformed(aPow16.Transformation()); + EXPECT_NEAR(aResult.X(), 16.0, 1e-9) << "Powered(16) should translate 16 units"; +} + +TEST(TopLoc_Location_Test, BinaryExponentiation_OddPowers) +{ + // Test binary exponentiation for odd powers (5, 7, 9) + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + gp_Pnt anOrigin(0, 0, 0); + + // Test power of 5 + TopLoc_Location aPow5 = aLoc.Powered(5); + gp_Pnt aResult = anOrigin.Transformed(aPow5.Transformation()); + EXPECT_NEAR(aResult.X(), 5.0, 1e-9) << "Powered(5) should translate 5 units"; + + // Test power of 7 + TopLoc_Location aPow7 = aLoc.Powered(7); + aResult = anOrigin.Transformed(aPow7.Transformation()); + EXPECT_NEAR(aResult.X(), 7.0, 1e-9) << "Powered(7) should translate 7 units"; + + // Test power of 9 + TopLoc_Location aPow9 = aLoc.Powered(9); + aResult = anOrigin.Transformed(aPow9.Transformation()); + EXPECT_NEAR(aResult.X(), 9.0, 1e-9) << "Powered(9) should translate 9 units"; +} + +TEST(TopLoc_Location_Test, BinaryExponentiation_VeryLargePower) +{ + // Test with very large power to verify efficiency improvement + // L^64 with binary exponentiation: only 6 squarings + // L^64 with naive: 63 multiplications + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(0.5, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + TopLoc_Location aPow64 = aLoc.Powered(64); + + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aPow64.Transformation()); + + EXPECT_NEAR(aResult.X(), 32.0, 1e-8) << "Powered(64) should translate 32 units (64 * 0.5)"; +} + +TEST(TopLoc_Location_Test, BinaryExponentiation_NegativePowers) +{ + // Test binary exponentiation for negative powers + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + // Test power of -4 (should be inverse of power 4) + TopLoc_Location aPowNeg4 = aLoc.Powered(-4); + TopLoc_Location aPow4 = aLoc.Powered(4); + + TopLoc_Location aIdentity = aPowNeg4 * aPow4; + EXPECT_TRUE(aIdentity.IsIdentity()) << "L^(-4) * L^4 should produce identity"; + + // Verify the transformation + gp_Pnt anOrigin(0, 0, 0); + gp_Pnt aResult = anOrigin.Transformed(aPowNeg4.Transformation()); + EXPECT_NEAR(aResult.X(), -4.0, 1e-9) << "Powered(-4) should translate -4 units"; +} + +TEST(TopLoc_Location_Test, BinaryExponentiation_RotationPowers) +{ + // Test binary exponentiation with rotation transformations + // 45 degrees rotation, power of 8 should equal 360 degrees (identity) + gp_Trsf aRot; + gp_Ax1 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)); + aRot.SetRotation(anAxis, M_PI / 4.0); // 45 degrees + + TopLoc_Location aLoc(aRot); + TopLoc_Location aPow8 = aLoc.Powered(8); // 8 * 45 degrees = 360 degrees + + // Should be close to identity (full rotation) + gp_Pnt aPnt(1, 0, 0); + gp_Pnt aResult = aPnt.Transformed(aPow8.Transformation()); + + EXPECT_NEAR(aResult.X(), 1.0, 1e-8); + EXPECT_NEAR(aResult.Y(), 0.0, 1e-8); + EXPECT_NEAR(aResult.Z(), 0.0, 1e-8); +} + +TEST(TopLoc_Location_Test, BinaryExponentiation_MixedEvenOdd) +{ + // Test a mix of even and odd powers to verify the algorithm handles both cases + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(2.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + gp_Pnt anOrigin(0, 0, 0); + + // Test 6 (even) + TopLoc_Location aPow6 = aLoc.Powered(6); + gp_Pnt aResult = anOrigin.Transformed(aPow6.Transformation()); + EXPECT_NEAR(aResult.X(), 12.0, 1e-9); + + // Test 11 (odd) + TopLoc_Location aPow11 = aLoc.Powered(11); + aResult = anOrigin.Transformed(aPow11.Transformation()); + EXPECT_NEAR(aResult.X(), 22.0, 1e-9); + + // Test 15 (odd) + TopLoc_Location aPow15 = aLoc.Powered(15); + aResult = anOrigin.Transformed(aPow15.Transformation()); + EXPECT_NEAR(aResult.X(), 30.0, 1e-9); +} diff --git a/src/FoundationClasses/TKMath/TopLoc/FILES.cmake b/src/FoundationClasses/TKMath/TopLoc/FILES.cmake index 44593cb6e0..e70862c03b 100644 --- a/src/FoundationClasses/TKMath/TopLoc/FILES.cmake +++ b/src/FoundationClasses/TKMath/TopLoc/FILES.cmake @@ -14,7 +14,6 @@ set(OCCT_TopLoc_FILES TopLoc_MapOfLocation.hxx TopLoc_SListNodeOfItemLocation.cxx TopLoc_SListNodeOfItemLocation.hxx - TopLoc_SListNodeOfItemLocation.lxx TopLoc_SListOfItemLocation.cxx TopLoc_SListOfItemLocation.hxx ) diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx index f8c8abdd31..79279209e0 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx @@ -44,13 +44,13 @@ public: Standard_EXPORT TopLoc_Datum3D(const gp_Trsf& T); //! Returns a gp_Trsf which, when applied to this datum, produces the default datum. - const gp_Trsf& Transformation() const { return myTrsf; } + const gp_Trsf& Transformation() const noexcept { return myTrsf; } //! Returns a gp_Trsf which, when applied to this datum, produces the default datum. - const gp_Trsf& Trsf() const { return myTrsf; } + const gp_Trsf& Trsf() const noexcept { return myTrsf; } //! Return transformation form. - gp_TrsfForm Form() const { return myTrsf.Form(); } + gp_TrsfForm Form() const noexcept { return myTrsf.Form(); } //! Dumps the content of me into the stream Standard_EXPORT void DumpJson(Standard_OStream& theOStream, Standard_Integer theDepth = -1) const; diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx index 9cbd5791fe..bf374e4059 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx @@ -24,7 +24,7 @@ TopLoc_ItemLocation::TopLoc_ItemLocation(const Handle(TopLoc_Datum3D)& D, const Standard_Integer P) : myDatum(D), myPower(P), - myTrsf(D->Transformation().Powered(P)) + myTrsf(P == 1 ? D->Transformation() : D->Transformation().Powered(P)) { } diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx index f78330be47..0ada67b56f 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx @@ -20,14 +20,11 @@ #include #include #include +#include #include //================================================================================================= -TopLoc_Location::TopLoc_Location() {} - -//================================================================================================= - TopLoc_Location::TopLoc_Location(const Handle(TopLoc_Datum3D)& D) { myItems.Construct(TopLoc_ItemLocation(D, 1)); @@ -108,26 +105,6 @@ TopLoc_Location TopLoc_Location::Multiplied(const TopLoc_Location& Other) const return result; } -//======================================================================= -// function : Divided -// purpose : operator / this*Other.Inverted() -//======================================================================= - -TopLoc_Location TopLoc_Location::Divided(const TopLoc_Location& Other) const -{ - return Multiplied(Other.Inverted()); -} - -//======================================================================= -// function : Predivided -// purpose : return Other.Inverted() * this -//======================================================================= - -TopLoc_Location TopLoc_Location::Predivided(const TopLoc_Location& Other) const -{ - return Other.Inverted().Multiplied(*this); -} - //================================================================================================= TopLoc_Location TopLoc_Location::Powered(const Standard_Integer pwr) const @@ -138,6 +115,8 @@ TopLoc_Location TopLoc_Location::Powered(const Standard_Integer pwr) const return *this; if (pwr == 0) return TopLoc_Location(); + if (pwr == 2) + return Squared(); // Fast path for most common case // optimisation when just one element if (myItems.Tail().IsEmpty()) @@ -147,41 +126,62 @@ TopLoc_Location TopLoc_Location::Powered(const Standard_Integer pwr) const return result; } + // Binary exponentiation for efficient computation: O(log n) instead of O(n) if (pwr > 0) - return Multiplied(Powered(pwr - 1)); + { + if (pwr % 2 == 0) + { + // Even power: L^n = (L^(n/2))^2 + return Powered(pwr / 2).Squared(); + } + else + { + // Odd power: L^n = L * L^(n-1) + return Multiplied(Powered(pwr - 1)); + } + } else + { return Inverted().Powered(-pwr); + } } //================================================================================================= -// two locations are Equal if the Items have the same LocalValues and Powers -// this is a recursive function to test it +// Two locations are Equal if the Items have the same LocalValues and Powers -Standard_Boolean TopLoc_Location::IsEqual(const TopLoc_Location& Other) const +Standard_Boolean TopLoc_Location::IsEqual(const TopLoc_Location& theOther) const noexcept { - const void** p = (const void**)&myItems; - const void** q = (const void**)&Other.myItems; - if (*p == *q) + // Fast path: same node means equal (handles identity == identity case too) + if (myItems.Node() == theOther.myItems.Node()) { return Standard_True; } - if (IsIdentity() || Other.IsIdentity()) - { - return Standard_False; - } - if (FirstDatum() != Other.FirstDatum()) + // Fast rejection: different cached hashes means not equal + if (myItems.HashCode() != theOther.myItems.HashCode()) { return Standard_False; } - if (FirstPower() != Other.FirstPower()) + // Hash collision: need element-by-element comparison + TopLoc_SListOfItemLocation anIt1 = myItems; + TopLoc_SListOfItemLocation anIt2 = theOther.myItems; + while (anIt1.More() && anIt2.More()) { - return Standard_False; - } - else - { - return NextLocation() == Other.NextLocation(); + if (anIt1.Node() == anIt2.Node()) + { + return Standard_True; // Shared tail + } + const TopLoc_ItemLocation& aItem1 = anIt1.Value(); + const TopLoc_ItemLocation& aItem2 = anIt2.Value(); + if (aItem1.myDatum != aItem2.myDatum || aItem1.myPower != aItem2.myPower) + { + return Standard_False; + } + anIt1.Next(); + anIt2.Next(); } + // Equal only if both exhausted + return !anIt1.More() && !anIt2.More(); } //================================================================================================= diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx index fcbe4d6fb3..6d566b71bb 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx @@ -39,7 +39,7 @@ public: //! Constructs an empty local coordinate system object. //! Note: A Location constructed from a default datum is said to be "empty". - Standard_EXPORT TopLoc_Location(); + TopLoc_Location() noexcept = default; //! Constructs the local coordinate system object defined //! by the transformation T. T invokes in turn, a TopLoc_Datum3D object. @@ -52,10 +52,10 @@ public: Standard_EXPORT TopLoc_Location(const Handle(TopLoc_Datum3D)& D); //! Returns true if this location is equal to the Identity transformation. - Standard_Boolean IsIdentity() const; + Standard_Boolean IsIdentity() const noexcept; //! Resets this location to the Identity transformation. - void Identity(); + void Identity() noexcept; //! Returns the first elementary datum of the //! Location. Use the NextLocation function recursively to access @@ -98,7 +98,10 @@ public: } //! Returns / . - Standard_NODISCARD Standard_EXPORT TopLoc_Location Divided(const TopLoc_Location& Other) const; + Standard_NODISCARD TopLoc_Location Divided(const TopLoc_Location& Other) const + { + return Multiplied(Other.Inverted()); + } Standard_NODISCARD TopLoc_Location operator/(const TopLoc_Location& Other) const { @@ -106,23 +109,30 @@ public: } //! Returns .Inverted() * . - Standard_NODISCARD Standard_EXPORT TopLoc_Location Predivided(const TopLoc_Location& Other) const; + Standard_NODISCARD TopLoc_Location Predivided(const TopLoc_Location& Other) const + { + return Other.Inverted().Multiplied(*this); + } //! Returns me at the power . If is zero //! returns Identity. can be lower than zero //! (usual meaning for powers). Standard_NODISCARD Standard_EXPORT TopLoc_Location Powered(const Standard_Integer pwr) const; + //! Returns the square of this location (optimized version of Powered(2)). + //! This is the most common power operation in actual usage. + Standard_NODISCARD TopLoc_Location Squared() const { return Multiplied(*this); } + //! Returns a hashed value for this local coordinate system. This value is used, with map tables, //! to store and retrieve the object easily //! @return a computed hash code - size_t HashCode() const; + size_t HashCode() const noexcept; //! Returns true if this location and the location Other //! have the same elementary data, i.e. contain the same //! series of TopLoc_Datum3D and respective powers. //! This method is an alias for operator ==. - Standard_EXPORT Standard_Boolean IsEqual(const TopLoc_Location& Other) const; + Standard_EXPORT Standard_Boolean IsEqual(const TopLoc_Location& Other) const noexcept; Standard_Boolean operator==(const TopLoc_Location& Other) const { return IsEqual(Other); } @@ -141,11 +151,10 @@ public: Standard_EXPORT void ShallowDump(Standard_OStream& S) const; //! Clear myItems - void Clear() { myItems.Clear(); } + void Clear() noexcept { myItems.Clear(); } - static Standard_Real ScalePrec() { return 1.e-14; } + static constexpr Standard_Real ScalePrec() { return 1.e-14; } -protected: private: TopLoc_SListOfItemLocation myItems; }; diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx index bfdc712186..e6a6519c4c 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx @@ -21,14 +21,14 @@ //================================================================================================= -inline Standard_Boolean TopLoc_Location::IsIdentity() const +inline Standard_Boolean TopLoc_Location::IsIdentity() const noexcept { return myItems.IsEmpty(); } //================================================================================================= -inline void TopLoc_Location::Identity() +inline void TopLoc_Location::Identity() noexcept { myItems.Clear(); } @@ -56,25 +56,10 @@ inline const TopLoc_Location& TopLoc_Location::NextLocation() const //================================================================================================= -inline size_t TopLoc_Location::HashCode() const +inline size_t TopLoc_Location::HashCode() const noexcept { - // Hashing base on IsEqual function - if (myItems.IsEmpty()) - { - return 0; - } - size_t aHash = opencascade::MurmurHash::optimalSeed(); - TopLoc_SListOfItemLocation items = myItems; - size_t aCombined[3]; - while (items.More()) - { - aCombined[0] = std::hash{}(items.Value().myDatum); - aCombined[1] = opencascade::hash(items.Value().myPower); - aCombined[2] = aHash; - aHash = opencascade::hashBytes(aCombined, sizeof(aCombined)); - items.Next(); - } - return aHash; + // Use cached hash from the list node + return myItems.HashCode(); } //================================================================================================= diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx index d364e57c29..d8cd5ac818 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx @@ -31,11 +31,23 @@ class TopLoc_SListNodeOfItemLocation : public Standard_Transient public: TopLoc_SListNodeOfItemLocation(const TopLoc_ItemLocation& I, - const TopLoc_SListOfItemLocation& aTail); + const TopLoc_SListOfItemLocation& aTail, + size_t theHash) + : myTail(aTail), + myValue(I), + myHash(theHash) + { + } - TopLoc_SListOfItemLocation& Tail() const; + TopLoc_SListOfItemLocation& Tail() { return myTail; } - TopLoc_ItemLocation& Value() const; + const TopLoc_SListOfItemLocation& Tail() const { return myTail; } + + TopLoc_ItemLocation& Value() { return myValue; } + + const TopLoc_ItemLocation& Value() const { return myValue; } + + size_t HashCode() const noexcept { return myHash; } DEFINE_STANDARD_RTTIEXT(TopLoc_SListNodeOfItemLocation, Standard_Transient) @@ -43,8 +55,7 @@ protected: private: TopLoc_SListOfItemLocation myTail; TopLoc_ItemLocation myValue; + size_t myHash; }; -#include - #endif // _TopLoc_SListNodeOfItemLocation_HeaderFile diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx deleted file mode 100644 index d25c20ef02..0000000000 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 1998-1999 Matra Datavision -// Copyright (c) 1999-2014 OPEN CASCADE SAS -// -// This file is part of Open CASCADE Technology software library. -// -// This library is free software; you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License version 2.1 as published -// by the Free Software Foundation, with special exception defined in the file -// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT -// distribution for complete text of the license and disclaimer of any warranty. -// -// Alternatively, this file may be used under the terms of Open CASCADE -// commercial license or contractual agreement. - -#include - -inline TopLoc_SListNodeOfItemLocation::TopLoc_SListNodeOfItemLocation( - const TopLoc_ItemLocation& I, - const TopLoc_SListOfItemLocation& T) - : myTail(T), - myValue(I) -{ -} - -inline TopLoc_SListOfItemLocation& TopLoc_SListNodeOfItemLocation::Tail() const -{ - return (TopLoc_SListOfItemLocation&)myTail; -} - -inline TopLoc_ItemLocation& TopLoc_SListNodeOfItemLocation::Value() const -{ - return (TopLoc_ItemLocation&)myValue; -} diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx index 1ba89df5d2..92ecae1454 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx @@ -15,16 +15,24 @@ // commercial license or contractual agreement. #include +#include #include #include #include +#include //================================================================================================= TopLoc_SListOfItemLocation::TopLoc_SListOfItemLocation(const TopLoc_ItemLocation& anItem, const TopLoc_SListOfItemLocation& aTail) - : myNode(new TopLoc_SListNodeOfItemLocation(anItem, aTail)) { + // Compute hash combining item's datum, power, and tail's cached hash + const size_t aCombined[3] = {std::hash{}(anItem.myDatum), + static_cast(anItem.myPower), + aTail.HashCode()}; + const size_t aHash = opencascade::hashBytes(aCombined, sizeof(aCombined)); + + myNode = new TopLoc_SListNodeOfItemLocation(anItem, aTail, aHash); if (!myNode->Tail().IsEmpty()) { const gp_Trsf& aT = myNode->Tail().Value().myTrsf; @@ -62,3 +70,10 @@ const TopLoc_SListOfItemLocation& TopLoc_SListOfItemLocation::Tail() const else return *this; } + +//================================================================================================= + +size_t TopLoc_SListOfItemLocation::HashCode() const noexcept +{ + return myNode.IsNull() ? 0 : myNode->HashCode(); +} diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx index 51f9e31b76..cbcc7bac61 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx @@ -46,7 +46,7 @@ public: DEFINE_STANDARD_ALLOC //! Creates an empty List. - TopLoc_SListOfItemLocation() {} + TopLoc_SListOfItemLocation() = default; //! Creates a List with as value and as tail. Standard_EXPORT TopLoc_SListOfItemLocation(const TopLoc_ItemLocation& anItem, @@ -82,10 +82,10 @@ public: } //! Return true if this list is empty - Standard_Boolean IsEmpty() const { return myNode.IsNull(); } + Standard_Boolean IsEmpty() const noexcept { return myNode.IsNull(); } //! Sets the list to be empty. - void Clear() { myNode.Nullify(); } + void Clear() noexcept { myNode.Nullify(); } //! Destructor ~TopLoc_SListOfItemLocation() { Clear(); } @@ -98,6 +98,14 @@ public: //! list the tail is the list itself. Standard_EXPORT const TopLoc_SListOfItemLocation& Tail() const; + //! Returns the cached hash of this list. + //! For an empty list, returns 0. + Standard_EXPORT size_t HashCode() const noexcept; + + //! Returns the underlying node handle. + //! Used for fast identity comparison between lists. + const Handle(TopLoc_SListNodeOfItemLocation)& Node() const noexcept { return myNode; } + //! Replaces the list by a list with as Value //! and the list as tail. void Construct(const TopLoc_ItemLocation& anItem) @@ -110,7 +118,7 @@ public: //! Returns True if the iterator has a current value. //! This is !IsEmpty() - Standard_Boolean More() const { return !IsEmpty(); } + Standard_Boolean More() const noexcept { return !IsEmpty(); } //! Moves the iterator to the next object in the list. //! If the iterator is empty it will stay empty. This is ToTail() diff --git a/src/FoundationClasses/TKernel/GTests/FILES.cmake b/src/FoundationClasses/TKernel/GTests/FILES.cmake index 5bc29c2ab9..2f1a9831f1 100644 --- a/src/FoundationClasses/TKernel/GTests/FILES.cmake +++ b/src/FoundationClasses/TKernel/GTests/FILES.cmake @@ -35,6 +35,5 @@ set(OCCT_TKernel_GTests_FILES Standard_Handle_Test.cxx TCollection_AsciiString_Test.cxx TCollection_ExtendedString_Test.cxx - TopLoc_Location_Test.cxx UnitsAPI_Test.cxx ) diff --git a/src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx b/src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx deleted file mode 100644 index 890e3fa85b..0000000000 --- a/src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2025 OPEN CASCADE SAS -// -// This file is part of Open CASCADE Technology software library. -// -// This library is free software; you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License version 2.1 as published -// by the Free Software Foundation, with special exception defined in the file -// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT -// distribution for complete text of the license and disclaimer of any warranty. -// -// Alternatively, this file may be used under the terms of Open CASCADE -// commercial license or contractual agreement. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace -{ -//! Functor for testing concurrent access to TopLoc_Location::Transformation() -struct TopLocTransformFunctor -{ - TopLocTransformFunctor(const std::vector& theShapeVec) - : myShapeVec(&theShapeVec), - myIsRaceDetected(0) - { - } - - void operator()(size_t i) const - { - if (!myIsRaceDetected) - { - const TopoDS_Vertex& aVertex = TopoDS::Vertex(myShapeVec->at(i)); - gp_Pnt aPoint = BRep_Tool::Pnt(aVertex); - if (aPoint.X() != static_cast(i)) - { - ++myIsRaceDetected; - } - } - } - - const std::vector* myShapeVec; - mutable std::atomic myIsRaceDetected; -}; -} // namespace - -TEST(TopLoc_Location_Test, OCC25545_ConcurrentTransformationAccess) -{ - // Bug OCC25545: TopLoc_Location::Transformation() provokes data races - // This test verifies that concurrent access to TopLoc_Location::Transformation() - // does not cause data races or incorrect geometry results - - // Place vertices in a vector, giving the i-th vertex the - // transformation that translates it on the vector (i,0,0) from the origin - Standard_Integer n = 1000; - std::vector aShapeVec(n); - std::vector aLocVec(n); - TopoDS_Shape aShape = BRepBuilderAPI_MakeVertex(gp::Origin()); - aShapeVec[0] = aShape; - - for (Standard_Integer i = 1; i < n; ++i) - { - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1, 0, 0)); - aLocVec[i] = aLocVec[i - 1] * aTrsf; - aShapeVec[i] = aShape.Moved(aLocVec[i]); - } - - // Evaluator function will access vertices geometry concurrently - TopLocTransformFunctor aFunc(aShapeVec); - - // Process concurrently - OSD_Parallel::For(0, n, aFunc); - - // Verify no data race was detected - EXPECT_EQ(aFunc.myIsRaceDetected, 0) - << "Data race detected in concurrent TopLoc_Location::Transformation() access"; -} -- 2.39.5