Browse Source

Added ScopeExit class (#1129)

This class helps to control the lifetime of temp allocations
Jorrit Rouwe 1 year ago
parent
commit
90477394cf

+ 49 - 0
Jolt/Core/ScopeExit.h

@@ -0,0 +1,49 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Core/NonCopyable.h>
+
+JPH_NAMESPACE_BEGIN
+
+/// Class that calls a function when it goes out of scope
+template <class F>
+class ScopeExit : public NonCopyable
+{
+public:
+	/// Constructor specifies the exit function
+	JPH_INLINE explicit	ScopeExit(F &&inFunction) : mFunction(std::move(inFunction)) { }
+
+	/// Destructor calls the exit function
+	JPH_INLINE			~ScopeExit() { if (!mInvoked) mFunction(); }
+
+	/// Call the exit function now instead of when going out of scope
+	JPH_INLINE void		Invoke()
+	{
+		if (!mInvoked)
+		{
+			mFunction();
+			mInvoked = true;
+		}
+	}
+
+	/// No longer call the exit function when going out of scope
+	JPH_INLINE void		Release()
+	{
+		mInvoked = true;
+	}
+
+private:
+	F					mFunction;
+	bool				mInvoked = false;
+};
+
+#define JPH_SCOPE_EXIT_TAG2(line)			scope_exit##line
+#define JPH_SCOPE_EXIT_TAG(line)			JPH_SCOPE_EXIT_TAG2(line)
+
+/// Usage: JPH_SCOPE_EXIT([]{ code to call on scope exit });
+#define JPH_SCOPE_EXIT(...) ScopeExit JPH_SCOPE_EXIT_TAG(__LINE__)(__VA_ARGS__)
+
+JPH_NAMESPACE_END

+ 1 - 0
Jolt/Jolt.cmake

@@ -56,6 +56,7 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Core/Result.h
 	${JOLT_PHYSICS_ROOT}/Core/Result.h
 	${JOLT_PHYSICS_ROOT}/Core/RTTI.cpp
 	${JOLT_PHYSICS_ROOT}/Core/RTTI.cpp
 	${JOLT_PHYSICS_ROOT}/Core/RTTI.h
 	${JOLT_PHYSICS_ROOT}/Core/RTTI.h
+	${JOLT_PHYSICS_ROOT}/Core/ScopeExit.h
 	${JOLT_PHYSICS_ROOT}/Core/Semaphore.cpp
 	${JOLT_PHYSICS_ROOT}/Core/Semaphore.cpp
 	${JOLT_PHYSICS_ROOT}/Core/Semaphore.h
 	${JOLT_PHYSICS_ROOT}/Core/Semaphore.h
 	${JOLT_PHYSICS_ROOT}/Core/StaticArray.h
 	${JOLT_PHYSICS_ROOT}/Core/StaticArray.h

+ 3 - 7
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -27,6 +27,7 @@
 #include <Jolt/Core/StreamIn.h>
 #include <Jolt/Core/StreamIn.h>
 #include <Jolt/Core/StreamOut.h>
 #include <Jolt/Core/StreamOut.h>
 #include <Jolt/Core/TempAllocator.h>
 #include <Jolt/Core/TempAllocator.h>
+#include <Jolt/Core/ScopeExit.h>
 #include <Jolt/Geometry/AABox4.h>
 #include <Jolt/Geometry/AABox4.h>
 #include <Jolt/Geometry/RayTriangle.h>
 #include <Jolt/Geometry/RayTriangle.h>
 #include <Jolt/Geometry/RayAABox.h>
 #include <Jolt/Geometry/RayAABox.h>
@@ -203,6 +204,7 @@ void HeightFieldShape::CalculateActiveEdges(uint inX, uint inY, uint inSizeX, ui
 	// Allocate temporary buffer for normals
 	// Allocate temporary buffer for normals
 	uint normals_size = 2 * inSizeX * inSizeY * sizeof(Vec3);
 	uint normals_size = 2 * inSizeX * inSizeY * sizeof(Vec3);
 	Vec3 *normals = (Vec3 *)inAllocator.Allocate(normals_size);
 	Vec3 *normals = (Vec3 *)inAllocator.Allocate(normals_size);
+	JPH_SCOPE_EXIT([&inAllocator, normals, normals_size]{ inAllocator.Free(normals, normals_size); });
 
 
 	// Calculate triangle normals and make normals zero for triangles that are missing
 	// Calculate triangle normals and make normals zero for triangles that are missing
 	Vec3 *out_normal = normals;
 	Vec3 *out_normal = normals;
@@ -312,9 +314,6 @@ void HeightFieldShape::CalculateActiveEdges(uint inX, uint inY, uint inSizeX, ui
 
 
 		global_bit_pos += 3 * (mSampleCount - 1 - inSizeX);
 		global_bit_pos += 3 * (mSampleCount - 1 - inSizeX);
 	}
 	}
-
-	// Free temporary buffer for normals
-	inAllocator.Free(normals, normals_size);
 }
 }
 
 
 void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSettings)
 void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSettings)
@@ -1252,6 +1251,7 @@ bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSiz
 	// Remap materials
 	// Remap materials
 	uint material_remap_table_size = uint(inMaterialList != nullptr? inMaterialList->size() : mMaterials.size());
 	uint material_remap_table_size = uint(inMaterialList != nullptr? inMaterialList->size() : mMaterials.size());
 	uint8 *material_remap_table = (uint8 *)inAllocator.Allocate(material_remap_table_size);
 	uint8 *material_remap_table = (uint8 *)inAllocator.Allocate(material_remap_table_size);
+	JPH_SCOPE_EXIT([&inAllocator, material_remap_table, material_remap_table_size]{ inAllocator.Free(material_remap_table, material_remap_table_size); });
 	if (inMaterialList != nullptr)
 	if (inMaterialList != nullptr)
 	{
 	{
 		// Conservatively reserve more space if the incoming material list is bigger
 		// Conservatively reserve more space if the incoming material list is bigger
@@ -1275,7 +1275,6 @@ bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSiz
 				if (mMaterials.size() >= 256)
 				if (mMaterials.size() >= 256)
 				{
 				{
 					// We can't have more than 256 materials since we use uint8 as indices
 					// We can't have more than 256 materials since we use uint8 as indices
-					inAllocator.Free(material_remap_table, material_remap_table_size);
 					return false;
 					return false;
 				}
 				}
 				*remap_entry = uint8(mMaterials.size());
 				*remap_entry = uint8(mMaterials.size());
@@ -1294,7 +1293,6 @@ bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSiz
 	if (mMaterials.size() == 1)
 	if (mMaterials.size() == 1)
 	{
 	{
 		// Only 1 material, we don't need to store the material indices
 		// Only 1 material, we don't need to store the material indices
-		inAllocator.Free(material_remap_table, material_remap_table_size);
 		return true;
 		return true;
 	}
 	}
 
 
@@ -1374,8 +1372,6 @@ bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSiz
 		}
 		}
 	}
 	}
 
 
-	// Free the remapping table
-	inAllocator.Free(material_remap_table, material_remap_table_size);
 	return true;
 	return true;
 }
 }
 
 

+ 4 - 5
Jolt/Physics/Collision/Shape/StaticCompoundShape.cpp

@@ -11,6 +11,7 @@
 #include <Jolt/Core/StreamIn.h>
 #include <Jolt/Core/StreamIn.h>
 #include <Jolt/Core/StreamOut.h>
 #include <Jolt/Core/StreamOut.h>
 #include <Jolt/Core/TempAllocator.h>
 #include <Jolt/Core/TempAllocator.h>
+#include <Jolt/Core/ScopeExit.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
@@ -216,10 +217,12 @@ StaticCompoundShape::StaticCompoundShape(const StaticCompoundShapeSettings &inSe
 	// Temporary storage for the bounding boxes of all shapes
 	// Temporary storage for the bounding boxes of all shapes
 	uint bounds_size = num_subshapes * sizeof(AABox);
 	uint bounds_size = num_subshapes * sizeof(AABox);
 	AABox *bounds = (AABox *)inTempAllocator.Allocate(bounds_size);
 	AABox *bounds = (AABox *)inTempAllocator.Allocate(bounds_size);
+	JPH_SCOPE_EXIT([&inTempAllocator, bounds, bounds_size]{ inTempAllocator.Free(bounds, bounds_size); });
 
 
 	// Temporary storage for body indexes (we're shuffling them)
 	// Temporary storage for body indexes (we're shuffling them)
 	uint body_idx_size = num_subshapes * sizeof(uint);
 	uint body_idx_size = num_subshapes * sizeof(uint);
 	uint *body_idx = (uint *)inTempAllocator.Allocate(body_idx_size);
 	uint *body_idx = (uint *)inTempAllocator.Allocate(body_idx_size);
+	JPH_SCOPE_EXIT([&inTempAllocator, body_idx, body_idx_size]{ inTempAllocator.Free(body_idx, body_idx_size); });
 
 
 	// Shift all shapes so that the center of mass is now at the origin and calculate bounds
 	// Shift all shapes so that the center of mass is now at the origin and calculate bounds
 	for (uint i = 0; i < num_subshapes; ++i)
 	for (uint i = 0; i < num_subshapes; ++i)
@@ -251,6 +254,7 @@ StaticCompoundShape::StaticCompoundShape(const StaticCompoundShapeSettings &inSe
 	};
 	};
 	uint stack_size = num_subshapes * sizeof(StackEntry);
 	uint stack_size = num_subshapes * sizeof(StackEntry);
 	StackEntry *stack = (StackEntry *)inTempAllocator.Allocate(stack_size);
 	StackEntry *stack = (StackEntry *)inTempAllocator.Allocate(stack_size);
+	JPH_SCOPE_EXIT([&inTempAllocator, stack, stack_size]{ inTempAllocator.Free(stack, stack_size); });
 	int top = 0;
 	int top = 0;
 
 
 	// Reserve enough space so that every sub shape gets its own leaf node
 	// Reserve enough space so that every sub shape gets its own leaf node
@@ -334,11 +338,6 @@ StaticCompoundShape::StaticCompoundShape(const StaticCompoundShapeSettings &inSe
 	mNodes.resize(next_node_idx);
 	mNodes.resize(next_node_idx);
 	mNodes.shrink_to_fit();
 	mNodes.shrink_to_fit();
 
 
-	// Free temporary memory
-	inTempAllocator.Free(stack, stack_size);
-	inTempAllocator.Free(body_idx, body_idx_size);
-	inTempAllocator.Free(bounds, bounds_size);
-
 	// Check if we ran out of bits for addressing a node
 	// Check if we ran out of bits for addressing a node
 	if (next_node_idx > IS_SUBSHAPE)
 	if (next_node_idx > IS_SUBSHAPE)
 	{
 	{

+ 2 - 3
Jolt/Physics/PhysicsSystem.cpp

@@ -30,6 +30,7 @@
 #include <Jolt/Core/JobSystem.h>
 #include <Jolt/Core/JobSystem.h>
 #include <Jolt/Core/TempAllocator.h>
 #include <Jolt/Core/TempAllocator.h>
 #include <Jolt/Core/QuickSort.h>
 #include <Jolt/Core/QuickSort.h>
+#include <Jolt/Core/ScopeExit.h>
 #ifdef JPH_DEBUG_RENDERER
 #ifdef JPH_DEBUG_RENDERER
 	#include <Jolt/Renderer/DebugRenderer.h>
 	#include <Jolt/Renderer/DebugRenderer.h>
 #endif // JPH_DEBUG_RENDERER
 #endif // JPH_DEBUG_RENDERER
@@ -1951,6 +1952,7 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi
 		// between body pairs if an earlier hit was found involving the body by another CCD body
 		// between body pairs if an earlier hit was found involving the body by another CCD body
 		// (if it's body ID < this CCD body's body ID - see filtering logic in CCDBroadPhaseCollector)
 		// (if it's body ID < this CCD body's body ID - see filtering logic in CCDBroadPhaseCollector)
 		CCDBody **sorted_ccd_bodies = (CCDBody **)temp_allocator->Allocate(num_ccd_bodies * sizeof(CCDBody *));
 		CCDBody **sorted_ccd_bodies = (CCDBody **)temp_allocator->Allocate(num_ccd_bodies * sizeof(CCDBody *));
+		JPH_SCOPE_EXIT([temp_allocator, sorted_ccd_bodies, num_ccd_bodies]{ temp_allocator->Free(sorted_ccd_bodies, num_ccd_bodies * sizeof(CCDBody *)); });
 		{
 		{
 			JPH_PROFILE("Sort");
 			JPH_PROFILE("Sort");
 
 
@@ -2191,9 +2193,6 @@ void PhysicsSystem::JobResolveCCDContacts(PhysicsUpdateContext *ioContext, Physi
 		// Notify change bounds on requested bodies
 		// Notify change bounds on requested bodies
 		if (num_bodies_to_update_bounds > 0)
 		if (num_bodies_to_update_bounds > 0)
 			mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
 			mBroadPhase->NotifyBodiesAABBChanged(bodies_to_update_bounds, num_bodies_to_update_bounds, false);
-
-		// Free the sorted ccd bodies
-		temp_allocator->Free(sorted_ccd_bodies, num_ccd_bodies * sizeof(CCDBody *));
 	}
 	}
 
 
 	// Ensure we free the CCD bodies array now, will not call the destructor!
 	// Ensure we free the CCD bodies array now, will not call the destructor!

+ 2 - 2
Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp

@@ -10,6 +10,7 @@
 #include <Jolt/Physics/SoftBody/SoftBodyManifold.h>
 #include <Jolt/Physics/SoftBody/SoftBodyManifold.h>
 #include <Jolt/Physics/PhysicsSystem.h>
 #include <Jolt/Physics/PhysicsSystem.h>
 #include <Jolt/Physics/Body/BodyManager.h>
 #include <Jolt/Physics/Body/BodyManager.h>
+#include <Jolt/Core/ScopeExit.h>
 #ifdef JPH_DEBUG_RENDERER
 #ifdef JPH_DEBUG_RENDERER
 	#include <Jolt/Renderer/DebugRenderer.h>
 	#include <Jolt/Renderer/DebugRenderer.h>
 #endif // JPH_DEBUG_RENDERER
 #endif // JPH_DEBUG_RENDERER
@@ -912,6 +913,7 @@ void SoftBodyMotionProperties::SkinVertices([[maybe_unused]] RMat44Arg inCenterO
 	uint num_skin_matrices = uint(mSettings->mInvBindMatrices.size());
 	uint num_skin_matrices = uint(mSettings->mInvBindMatrices.size());
 	uint skin_matrices_size = num_skin_matrices * sizeof(Mat44);
 	uint skin_matrices_size = num_skin_matrices * sizeof(Mat44);
 	Mat44 *skin_matrices = (Mat44 *)ioTempAllocator.Allocate(skin_matrices_size);
 	Mat44 *skin_matrices = (Mat44 *)ioTempAllocator.Allocate(skin_matrices_size);
+	JPH_SCOPE_EXIT([&ioTempAllocator, skin_matrices, skin_matrices_size]{ ioTempAllocator.Free(skin_matrices, skin_matrices_size); });
 	const Mat44 *skin_matrices_end = skin_matrices + num_skin_matrices;
 	const Mat44 *skin_matrices_end = skin_matrices + num_skin_matrices;
 	const InvBind *inv_bind_matrix = mSettings->mInvBindMatrices.data();
 	const InvBind *inv_bind_matrix = mSettings->mInvBindMatrices.data();
 	for (Mat44 *s = skin_matrices; s < skin_matrices_end; ++s, ++inv_bind_matrix)
 	for (Mat44 *s = skin_matrices; s < skin_matrices_end; ++s, ++inv_bind_matrix)
@@ -968,8 +970,6 @@ void SoftBodyMotionProperties::SkinVertices([[maybe_unused]] RMat44Arg inCenterO
 		mSkinState[s.mVertex].mNormal = normal;
 		mSkinState[s.mVertex].mNormal = normal;
 	}
 	}
 
 
-	ioTempAllocator.Free(skin_matrices, skin_matrices_size);
-
 	if (inHardSkinAll)
 	if (inHardSkinAll)
 	{
 	{
 		// Hard skin all vertices and reset their velocities
 		// Hard skin all vertices and reset their velocities

+ 46 - 0
UnitTests/Core/ScopeExitTest.cpp

@@ -0,0 +1,46 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include "UnitTestFramework.h"
+#include <Jolt/Core/ScopeExit.h>
+
+TEST_SUITE("ScopeExitTest")
+{
+	TEST_CASE("TestScopeExitOrder")
+	{
+		int value = 0;
+		{
+			// Last created should be first destroyed
+			JPH_SCOPE_EXIT([&value]{ CHECK(value == 1); value = 2; });
+			JPH_SCOPE_EXIT([&value]{ CHECK(value == 0); value = 1; });
+			CHECK(value == 0);
+		}
+		CHECK(value == 2);
+	}
+
+	TEST_CASE("TestScopeExitRelease")
+	{
+		int value = 0;
+		{
+			ScopeExit scope_exit([&value]{ value++; });
+			CHECK(value == 0);
+			// Don't call the exit function anymore
+			scope_exit.Release();
+		}
+		CHECK(value == 0);
+	}
+
+	TEST_CASE("TestScopeExitInvoke")
+	{
+		int value = 0;
+		{
+			ScopeExit scope_exit([&value]{ value++; });
+			CHECK(value == 0);
+			scope_exit.Invoke();
+			CHECK(value == 1);
+			// Should not call again on exit
+		}
+		CHECK(value == 1);
+	}
+}

+ 1 - 0
UnitTests/UnitTests.cmake

@@ -9,6 +9,7 @@ set(UNIT_TESTS_SRC_FILES
 	${UNIT_TESTS_ROOT}/Core/JobSystemTest.cpp
 	${UNIT_TESTS_ROOT}/Core/JobSystemTest.cpp
 	${UNIT_TESTS_ROOT}/Core/LinearCurveTest.cpp
 	${UNIT_TESTS_ROOT}/Core/LinearCurveTest.cpp
 	${UNIT_TESTS_ROOT}/Core/PreciseMathTest.cpp
 	${UNIT_TESTS_ROOT}/Core/PreciseMathTest.cpp
+	${UNIT_TESTS_ROOT}/Core/ScopeExitTest.cpp
 	${UNIT_TESTS_ROOT}/Core/StringToolsTest.cpp
 	${UNIT_TESTS_ROOT}/Core/StringToolsTest.cpp
 	${UNIT_TESTS_ROOT}/Core/QuickSortTest.cpp
 	${UNIT_TESTS_ROOT}/Core/QuickSortTest.cpp
 	${UNIT_TESTS_ROOT}/doctest.h
 	${UNIT_TESTS_ROOT}/doctest.h