Browse Source

When calling PhysicsSystem::Update with a delta time of 0, contact remove callbacks were triggered by accident for all existing contacts. (#1525)

Jorrit Rouwe 5 months ago
parent
commit
cdca4c3b51

+ 2 - 0
Docs/ReleaseNotes.md

@@ -49,6 +49,8 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 * Fixed running out of stack space when simulating a really high number of active rigid bodies.
 * Moved the 'broad phase bit' to the highest bit in `BodyID` to avoid running out of `NodeID`s in `BroadPhaseQuadTree` when calling `PhysicsSystem::OptimizeBroadPhase` on a tree with a very high body count.
 * `TempAllocatorImpl` uses 64 bit integers internally to allow for a higher max contact constraint count.
+* When inserting lots of bodies without using batching, a broad phase tree of depth > 128 can be created. If the `PhysicsSystem` was destructed in this situation, a stack overflow would cause a crash.
+* When calling `PhysicsSystem::Update` with a delta time of 0, contact remove callbacks were triggered by accident for all existing contacts.
 
 ## v5.2.0
 

+ 3 - 2
Jolt/Physics/PhysicsSystem.cpp

@@ -154,8 +154,9 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 		mBroadPhase->UpdateFinalize(update_state);
 		mBroadPhase->UnlockModifications();
 
-		// Call contact removal callbacks from contacts that existed in the previous update
-		mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(0, 0);
+		// If time has passed, call contact removal callbacks from contacts that existed in the previous update
+		if (inDeltaTime > 0.0f)
+			mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(0, 0);
 
 		mBodyManager.UnlockAllBodies();
 		return EPhysicsUpdateError::None;

+ 48 - 0
UnitTests/Physics/ContactListenerTests.cpp

@@ -674,4 +674,52 @@ TEST_SUITE("ContactListenerTests")
 										CHECK(body2.GetLinearVelocity() == cInitialVelocity2);
 								}
 	}
+
+	// Test that an update with zero delta time doesn't generate contact callbacks
+	TEST_CASE("TestZeroDeltaTime")
+	{
+		PhysicsTestContext c;
+
+		// Register listener
+		LoggingContactListener listener;
+		c.GetSystem()->SetContactListener(&listener);
+
+		// Create a sphere that intersects with the floor
+		Body &floor = c.CreateFloor();
+		Body &body1 = c.CreateSphere(RVec3::sZero(), 1.0f, EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING);
+
+		// Step with zero delta time
+		c.GetSystem()->Update(0.0f, 1, c.GetTempAllocator(), c.GetJobSystem());
+
+		// No callbacks should trigger when delta time is zero
+		CHECK(listener.GetEntryCount() == 0);
+
+		// Simulate for 1 step
+		c.SimulateSingleStep();
+
+		// We expect a validate and a contact point added message
+		CHECK(listener.GetEntryCount() == 2);
+		CHECK(listener.Contains(EType::Validate, floor.GetID(), body1.GetID()));
+		CHECK(listener.Contains(EType::Add, floor.GetID(), body1.GetID()));
+		listener.Clear();
+
+		// Create a 2nd sphere that intersects with the floor
+		Body &body2 = c.CreateSphere(RVec3(4, 0, 0), 1.0f, EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING);
+
+		// Step with zero delta time
+		c.GetSystem()->Update(0.0f, 1, c.GetTempAllocator(), c.GetJobSystem());
+
+		// No callbacks should trigger when delta time is zero
+		CHECK(listener.GetEntryCount() == 0);
+
+		// Simulate for 1 step
+		c.SimulateSingleStep();
+
+		// We expect callbacks for both bodies now
+		CHECK(listener.GetEntryCount() == 4);
+		CHECK(listener.Contains(EType::Validate, floor.GetID(), body1.GetID()));
+		CHECK(listener.Contains(EType::Persist, floor.GetID(), body1.GetID()));
+		CHECK(listener.Contains(EType::Validate, floor.GetID(), body2.GetID()));
+		CHECK(listener.Contains(EType::Add, floor.GetID(), body2.GetID()));
+	}
 }

+ 12 - 0
UnitTests/PhysicsTestContext.h

@@ -88,6 +88,18 @@ public:
 		return mDeltaTime / mCollisionSteps;
 	}
 
+	// Get the temporary allocator
+	TempAllocator *		GetTempAllocator() const
+	{
+		return mTempAllocator;
+	}
+
+	// Get the job system
+	JobSystem *			GetJobSystem() const
+	{
+		return mJobSystem;
+	}
+
 #ifdef JPH_DEBUG_RENDERER
 	// Write the debug output to a file to be able to replay it with JoltViewer
 	void				RecordDebugOutput(const char *inFileName);