feat(physics): internalize jolt bodyid entity mapping inside the physics plugin#500
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Entity::Kill and EntityId hashing; introduces a BiMap utility and BodyEntityMap resource; wires BodyID↔Entity synchronization into rigid/soft body systems with entt hooks and plugin registration; updates physics tests and adds commit-message guidance. Changes
Sequence Diagram(s)sequenceDiagram
actor Dev as Developer
participant Engine as Engine (Core)
participant Plugin as PhysicsPlugin
participant System as RigidBodySystem
participant Map as BodyEntityMap
participant JPH as Jolt Physics
Dev->>Engine: create Entity + add RigidBody/SoftBody
Engine->>System: notify construct handlers
System->>JPH: create physics body
JPH-->>System: return BodyID
System->>Map: Add(EntityId, BodyID)
Map-->>System: acknowledge stored
Dev->>Engine: destroy Entity (or call Entity::Kill)
Engine->>System: notify destroy handlers
System->>Map: Remove(EntityId / BodyID)
Map-->>System: acknowledge removed
System->>JPH: destroy physics body
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/plugin/physics/src/system/SoftBodySystem.cpp (1)
546-555:⚠️ Potential issue | 🟡 MinorMissing disconnect for
OnSoftBodyInternalDestroyin shutdown.
InitSoftBodySystemconnectsOnSoftBodyInternalDestroy(line 541), butShutdownSoftBodySystemdoes not disconnect it. This could cause issues if the system is reinitialized or if callbacks fire after shutdown.🔧 Suggested fix
void ShutdownSoftBodySystem(Engine::Core &core) { auto ®istry = core.GetRegistry(); // Disconnect hooks registry.on_construct<Component::SoftBody>().disconnect<&OnSoftBodyConstruct>(); registry.on_destroy<Component::SoftBody>().disconnect<&OnSoftBodyDestroy>(); + registry.on_destroy<Component::SoftBodyInternal>().disconnect<&OnSoftBodyInternalDestroy>(); Log::Debug("SoftBodySystem shutdown"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 546 - 555, ShutdownSoftBodySystem is missing a disconnect for the handler registered in InitSoftBodySystem: add a disconnect call for OnSoftBodyInternalDestroy in ShutdownSoftBodySystem (mirror the existing disconnects for OnSoftBodyConstruct/OnSoftBodyDestroy) so the registry no longer holds the callback (i.e., call registry.on_destroy<Component::SoftBodyInternal>().disconnect<&OnSoftBodyInternalDestroy>(); or the exact event type used when connecting) to prevent callbacks after shutdown.src/plugin/physics/src/system/RigidBodySystem.cpp (1)
411-420:⚠️ Potential issue | 🟡 Minor
OnRigidBodyInternalConstructis dead code and should be removed.
OnRigidBodyInternalConstruct(lines 331-345) is implemented to add mappings toBodyEntityMap, but it is never registered inInitRigidBodySystem. Only the destroy hook is connected (line 419).The function cannot be triggered because
RigidBodyInternalcomponents are only ever created internally viaOnRigidBodyConstruct(line 319), and the mapping toBodyEntityMapalready occurs there. No external construction ofRigidBodyInternalexists in the codebase.Either remove the unused function or add a comment explaining why it exists.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/RigidBodySystem.cpp` around lines 411 - 420, Remove the dead function OnRigidBodyInternalConstruct (or add a comment) because it is never registered in InitRigidBodySystem and its behavior (adding mappings to BodyEntityMap) is already handled in OnRigidBodyConstruct; specifically delete OnRigidBodyInternalConstruct (or, if you prefer to keep it, add a clear comment on top of OnRigidBodyInternalConstruct explaining it is unused/kept for historical reasons) and ensure InitRigidBodySystem only registers the actual handlers (OnRigidBodyConstruct, OnRigidBodyDestroy, OnRigidBodyInternalDestroy) so there is no misleading, unreferenced code.
🧹 Nitpick comments (7)
tools/ai/copilot/copilot-commit-message-instructions.md (1)
5-13: Consider adding a language specifier to the fenced code block.The fenced code block lacks a language identifier. Adding
textwould satisfy the linter and improve rendering consistency across markdown viewers.✨ Proposed improvement
-``` +```text <type>(<scope>): <short summary> │ │ │ │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end. │ │ │ └─⫸ Commit Scope │ └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test|chore ```As per static analysis hints, markdownlint flags fenced code blocks without language specifiers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/ai/copilot/copilot-commit-message-instructions.md` around lines 5 - 13, The fenced code block that contains the commit template starting with "<type>(<scope>): <short summary>" lacks a language specifier; update the opening fence from ``` to ```text so the block becomes "```text" (and keep the closing fence as ```), which satisfies markdownlint and ensures consistent rendering.src/plugin/physics/src/utils/BiMap.hpp (1)
7-10: Consider explicitly deleting copy/move operations.The class stores pointers to keys within its internal maps. The implicitly-generated copy constructor and copy assignment operator would copy these pointers, which would then point into the source object's maps—causing undefined behavior if the copy is used after the source is modified or destroyed.
Since
BodyEntityMapis used as a singleton resource, this may not be an immediate problem, but explicitly deleting these operations would prevent accidental misuse.♻️ Suggested change
template <typename TLeft, typename TRight> class BiMap { public: BiMap() = default; ~BiMap() = default; + BiMap(const BiMap &) = delete; + BiMap &operator=(const BiMap &) = delete; + BiMap(BiMap &&) = delete; + BiMap &operator=(BiMap &&) = delete;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/utils/BiMap.hpp` around lines 7 - 10, The BiMap template should explicitly disable copying/moving to avoid dangling internal pointer issues; update the BiMap class declaration (and any singleton wrapper like BodyEntityMap usage) to delete the copy constructor, copy assignment operator, move constructor, and move assignment operator (e.g., declare BiMap(const BiMap&) = delete; BiMap& operator=(const BiMap&) = delete; BiMap(BiMap&&) = delete; BiMap& operator=(BiMap&&) = delete;) so instances cannot be copied or moved accidentally and pointers into its internal maps remain valid.src/plugin/physics/tests/BodyEntityMapTest.cpp (1)
5-77: Good test coverage for basic operations.The tests cover addition, removal, retrieval, and containment checks. Consider adding tests for edge cases:
- Updating an existing mapping — verifying that
Add(existingLeft, newRight)properly replaces the mapping (this would catch the bug identified inBiMap::Add)- Removing non-existent keys — ensuring
Remove()is safe when the key doesn't existGet()on missing key — documenting that it throwsstd::out_of_range🧪 Example additional test case for update behavior
TEST(PhysicsPlugin, BodyEntityMapUpdate) { Physics::Resource::BodyEntityMap map; map.Add(Engine::EntityId{1}, JPH::BodyID{10}); EXPECT_EQ(map.Get(Engine::EntityId{1}), JPH::BodyID{10}); // Update mapping for same entity to different body map.Add(Engine::EntityId{1}, JPH::BodyID{20}); EXPECT_EQ(map.Size(), 1); // Should still be 1, not 2 EXPECT_EQ(map.Get(Engine::EntityId{1}), JPH::BodyID{20}); EXPECT_FALSE(map.Contains(JPH::BodyID{10})); // Old body should be removed }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/tests/BodyEntityMapTest.cpp` around lines 5 - 77, Tests miss edge cases: update existing mapping, removing non-existent keys, and Get() on missing keys; add tests and fix BiMap::Add so Add(existingLeft, newRight) replaces the old right and updates both directions. Specifically, modify BiMap::Add to check if left or right already exist and remove their old pairings before inserting (ensure it updates both maps used by Physics::Resource::BodyEntityMap's Add/Remove/Get/Contains), make Remove safe when key absent (no-ops), and ensure Get throws std::out_of_range for missing keys so tests can assert that behavior; then add the suggested TEST PhysicsPlugin.BodyEntityMapUpdate plus cases for Remove non-existent and Get missing-key.src/plugin/physics/src/system/SoftBodySystem.cpp (2)
446-447: Same duplicate addition issue as RigidBodySystem.
OnSoftBodyConstructadds toBodyEntityMap(line 447), andOnSoftBodyInternalConstruct(lines 461-476) would also add when triggered. However,OnSoftBodyInternalConstructis not registered inInitSoftBodySystem, making it dead code.For consistency with the destruction pattern, either:
- Remove the dead
OnSoftBodyInternalConstructfunction, or- Register it and remove the
Addcall fromOnSoftBodyConstructAlso applies to: 461-476
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 446 - 447, The code currently duplicates BodyEntityMap additions because OnSoftBodyConstruct calls core.GetResource<Resource::BodyEntityMap>().Add(entity, bodyID) while OnSoftBodyInternalConstruct (defined but not used) would also add the same mapping; fix by choosing one approach: either remove the dead OnSoftBodyInternalConstruct function entirely, or register it in InitSoftBodySystem and remove the Add(...) call from OnSoftBodyConstruct so the mapping is added only inside OnSoftBodyInternalConstruct; update InitSoftBodySystem to subscribe the OnSoftBodyInternalConstruct handler if you pick registration, or delete the OnSoftBodyInternalConstruct symbol and any unused references if you pick removal to match the destruction pattern.
502-503: Duplicate removal from BodyEntityMap (same as RigidBodySystem).
OnSoftBodyDestroyremoves fromBodyEntityMap(line 502) and then removesSoftBodyInternal(line 503), which triggersOnSoftBodyInternalDestroyto remove again (line 525).Apply the same fix as suggested for RigidBodySystem - let
OnSoftBodyInternalDestroyhandle the removal consistently.♻️ Suggested consolidation
Log::Debug(fmt::format("Destroyed SoftBody for entity {}", entity)); - core.GetResource<Resource::BodyEntityMap>().Remove(internal->bodyID); entity.RemoveComponent<Component::SoftBodyInternal>();Also applies to: 511-526
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 502 - 503, OnSoftBodyDestroy currently removes the body from BodyEntityMap and then removes the SoftBodyInternal component which causes OnSoftBodyInternalDestroy to remove the same entry again; remove the explicit call to core.GetResource<Resource::BodyEntityMap>().Remove(internal->bodyID) from OnSoftBodyDestroy and only call entity.RemoveComponent<Component::SoftBodyInternal>() so that OnSoftBodyInternalDestroy is the single place that performs core.GetResource<Resource::BodyEntityMap>().Remove(bodyID); apply the same change for the duplicated block in the 511-526 region to consolidate removal logic.src/plugin/physics/src/system/RigidBodySystem.cpp (1)
382-383: Duplicate removal from BodyEntityMap.
OnRigidBodyDestroyremoves the mapping fromBodyEntityMap(line 382) and then removes theRigidBodyInternalcomponent (line 383). Removing the component triggersOnRigidBodyInternalDestroy, which attempts to remove the same mapping again (line 404).This mirrors the duplicate addition issue. Consider removing the
BodyEntityMap.Remove()call fromOnRigidBodyDestroy(line 382) and lettingOnRigidBodyInternalDestroyhandle it consistently.♻️ Suggested consolidation
Log::Debug(fmt::format("Destroyed RigidBody for entity {} with BodyID {}", entity, internalComponent->bodyID.GetIndexAndSequenceNumber())); - core->GetResource<Resource::BodyEntityMap>().Remove(internalComponent->bodyID); entity.RemoveComponent<Component::RigidBodyInternal>();This lets
OnRigidBodyInternalDestroyhandle the map removal consistently.Also applies to: 391-405
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/RigidBodySystem.cpp` around lines 382 - 383, OnRigidBodyDestroy currently calls core->GetResource<Resource::BodyEntityMap>().Remove(internalComponent->bodyID) and then entity.RemoveComponent<Component::RigidBodyInternal>(), which causes OnRigidBodyInternalDestroy to attempt the same removal again; remove the explicit BodyEntityMap.Remove(...) call from OnRigidBodyDestroy and let OnRigidBodyInternalDestroy perform the single authoritative removal of the bodyID mapping when handling the Component::RigidBodyInternal destruction (ensure any other paths that remove the component rely on that single removal).src/plugin/physics/tests/BodyEntitySyncTest.cpp (1)
46-86: Missing error policy setting may cause test instability.
BodyEntityMapRemovaltest (lines 46-86) does not callSetErrorPolicyForAllSchedulers, while other tests do (lines 17, 92). Ifentity.Kill()or other operations throw/log errors, this inconsistency could lead to different test behaviors.🔧 Suggested fix
TEST(PhysicsPlugin, BodyEntityMapRemoval) { Engine::Core core; + core.SetErrorPolicyForAllSchedulers(Engine::Scheduler::SchedulerErrorPolicy::Nothing); + core.AddPlugins<Physics::Plugin>();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/tests/BodyEntitySyncTest.cpp` around lines 46 - 86, The test BodyEntityMapRemoval omits calling SetErrorPolicyForAllSchedulers on the Engine::Core instance, causing inconsistent error-handling compared to other tests; update the test to invoke core.SetErrorPolicyForAllSchedulers(...) (using the same ErrorPolicy used in the other tests) during setup—before core.RunSystems() (for example right after core.AddPlugins or inside the core.RegisterSystem setup) so that entity.Kill() and other operations use the same error policy and the test behaves consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/plugin/physics/src/resource/BodyEntityMap.hpp`:
- Around line 12-13: BodyEntityMap instantiation fails because JPH::BodyID lacks
a std::hash specialization required by Utils::BiMap; add a
std::hash<JPH::BodyID> specialization that converts the BodyID to a stable
integral value (e.g., call the BodyID accessor that returns its raw/index value
or cast to its underlying integral type) and returns
std::hash<std::underlying_type_t<...>> applied to that value; place the
specialization in namespace std so unordered_map can use it and then
BodyEntityMap (aliasing Utils::BiMap<Engine::EntityId, JPH::BodyID>) will
compile.
In `@src/plugin/physics/src/system/RigidBodySystem.cpp`:
- Around line 319-320: Remove the redundant call to BodyEntityMap::Add in
OnRigidBodyConstruct: when you call
entity.AddComponent<Component::RigidBodyInternal>(bodyID) the
OnRigidBodyInternalConstruct handler will perform the same
core->GetResource<Resource::BodyEntityMap>().Add(entity, bodyID) mapping, so
delete the explicit Add() line after AddComponent. Update OnRigidBodyConstruct
to only add the RigidBodyInternal component and rely on
OnRigidBodyInternalConstruct (and the existing BiMap::Add behavior) to insert
the mapping.
In `@src/plugin/physics/src/system/SoftBodySystem.cpp`:
- Around line 471-472: The warning in OnSoftBodyInternalConstruct incorrectly
says "skipping body removal" for SoftBodyInternal; update the Log::Warn call in
the OnSoftBodyInternalConstruct handler to state "skipping body addition to
BodyEntityMap" (or similar) so it accurately reflects the construct path and
mentions SoftBodyInternal and BodyEntityMap for clarity; keep the early return
behavior unchanged.
In `@src/plugin/physics/src/utils/BiMap.hpp`:
- Around line 12-19: Add currently uses insert_or_assign on _leftToRight and
_rightToLeft but never cleans up the previous opposite mapping, leaving stale
entries; modify Add(const TLeft &left, const TRight &right) to: check if left
already exists in _leftToRight and if so get the old right (via the stored
pointer) and erase that old right from _rightToLeft before inserting the new
mapping; similarly check if right already exists in _rightToLeft and if so erase
its old left from _leftToRight to avoid orphaned pointers; then proceed to
insert_or_assign the new left→right and right→left and update the stored pointer
fields so both maps remain consistent (refer to Add, _leftToRight, _rightToLeft,
insert_or_assign).
In `@tools/ai/copilot/copilot-commit-message-instructions.md`:
- Line 49: Update the guideline text that reads "* commit must be in english" to
capitalize the language name: change "english" to "English" (locate the exact
string "* commit must be in english" in copilot-commit-message-instructions.md
and replace it so the rule reads "* commit must be in English").
---
Outside diff comments:
In `@src/plugin/physics/src/system/RigidBodySystem.cpp`:
- Around line 411-420: Remove the dead function OnRigidBodyInternalConstruct (or
add a comment) because it is never registered in InitRigidBodySystem and its
behavior (adding mappings to BodyEntityMap) is already handled in
OnRigidBodyConstruct; specifically delete OnRigidBodyInternalConstruct (or, if
you prefer to keep it, add a clear comment on top of
OnRigidBodyInternalConstruct explaining it is unused/kept for historical
reasons) and ensure InitRigidBodySystem only registers the actual handlers
(OnRigidBodyConstruct, OnRigidBodyDestroy, OnRigidBodyInternalDestroy) so there
is no misleading, unreferenced code.
In `@src/plugin/physics/src/system/SoftBodySystem.cpp`:
- Around line 546-555: ShutdownSoftBodySystem is missing a disconnect for the
handler registered in InitSoftBodySystem: add a disconnect call for
OnSoftBodyInternalDestroy in ShutdownSoftBodySystem (mirror the existing
disconnects for OnSoftBodyConstruct/OnSoftBodyDestroy) so the registry no longer
holds the callback (i.e., call
registry.on_destroy<Component::SoftBodyInternal>().disconnect<&OnSoftBodyInternalDestroy>();
or the exact event type used when connecting) to prevent callbacks after
shutdown.
---
Nitpick comments:
In `@src/plugin/physics/src/system/RigidBodySystem.cpp`:
- Around line 382-383: OnRigidBodyDestroy currently calls
core->GetResource<Resource::BodyEntityMap>().Remove(internalComponent->bodyID)
and then entity.RemoveComponent<Component::RigidBodyInternal>(), which causes
OnRigidBodyInternalDestroy to attempt the same removal again; remove the
explicit BodyEntityMap.Remove(...) call from OnRigidBodyDestroy and let
OnRigidBodyInternalDestroy perform the single authoritative removal of the
bodyID mapping when handling the Component::RigidBodyInternal destruction
(ensure any other paths that remove the component rely on that single removal).
In `@src/plugin/physics/src/system/SoftBodySystem.cpp`:
- Around line 446-447: The code currently duplicates BodyEntityMap additions
because OnSoftBodyConstruct calls
core.GetResource<Resource::BodyEntityMap>().Add(entity, bodyID) while
OnSoftBodyInternalConstruct (defined but not used) would also add the same
mapping; fix by choosing one approach: either remove the dead
OnSoftBodyInternalConstruct function entirely, or register it in
InitSoftBodySystem and remove the Add(...) call from OnSoftBodyConstruct so the
mapping is added only inside OnSoftBodyInternalConstruct; update
InitSoftBodySystem to subscribe the OnSoftBodyInternalConstruct handler if you
pick registration, or delete the OnSoftBodyInternalConstruct symbol and any
unused references if you pick removal to match the destruction pattern.
- Around line 502-503: OnSoftBodyDestroy currently removes the body from
BodyEntityMap and then removes the SoftBodyInternal component which causes
OnSoftBodyInternalDestroy to remove the same entry again; remove the explicit
call to core.GetResource<Resource::BodyEntityMap>().Remove(internal->bodyID)
from OnSoftBodyDestroy and only call
entity.RemoveComponent<Component::SoftBodyInternal>() so that
OnSoftBodyInternalDestroy is the single place that performs
core.GetResource<Resource::BodyEntityMap>().Remove(bodyID); apply the same
change for the duplicated block in the 511-526 region to consolidate removal
logic.
In `@src/plugin/physics/src/utils/BiMap.hpp`:
- Around line 7-10: The BiMap template should explicitly disable copying/moving
to avoid dangling internal pointer issues; update the BiMap class declaration
(and any singleton wrapper like BodyEntityMap usage) to delete the copy
constructor, copy assignment operator, move constructor, and move assignment
operator (e.g., declare BiMap(const BiMap&) = delete; BiMap& operator=(const
BiMap&) = delete; BiMap(BiMap&&) = delete; BiMap& operator=(BiMap&&) = delete;)
so instances cannot be copied or moved accidentally and pointers into its
internal maps remain valid.
In `@src/plugin/physics/tests/BodyEntityMapTest.cpp`:
- Around line 5-77: Tests miss edge cases: update existing mapping, removing
non-existent keys, and Get() on missing keys; add tests and fix BiMap::Add so
Add(existingLeft, newRight) replaces the old right and updates both directions.
Specifically, modify BiMap::Add to check if left or right already exist and
remove their old pairings before inserting (ensure it updates both maps used by
Physics::Resource::BodyEntityMap's Add/Remove/Get/Contains), make Remove safe
when key absent (no-ops), and ensure Get throws std::out_of_range for missing
keys so tests can assert that behavior; then add the suggested TEST
PhysicsPlugin.BodyEntityMapUpdate plus cases for Remove non-existent and Get
missing-key.
In `@src/plugin/physics/tests/BodyEntitySyncTest.cpp`:
- Around line 46-86: The test BodyEntityMapRemoval omits calling
SetErrorPolicyForAllSchedulers on the Engine::Core instance, causing
inconsistent error-handling compared to other tests; update the test to invoke
core.SetErrorPolicyForAllSchedulers(...) (using the same ErrorPolicy used in the
other tests) during setup—before core.RunSystems() (for example right after
core.AddPlugins or inside the core.RegisterSystem setup) so that entity.Kill()
and other operations use the same error policy and the test behaves
consistently.
In `@tools/ai/copilot/copilot-commit-message-instructions.md`:
- Around line 5-13: The fenced code block that contains the commit template
starting with "<type>(<scope>): <short summary>" lacks a language specifier;
update the opening fence from ``` to ```text so the block becomes "```text" (and
keep the closing fence as ```), which satisfies markdownlint and ensures
consistent rendering.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ba56f620-c1d1-4b23-a020-39bcc948c7f9
📒 Files selected for processing (12)
src/engine/src/entity/Entity.cppsrc/engine/src/entity/Entity.hppsrc/engine/src/entity/EntityId.hppsrc/plugin/physics/src/Physics.hppsrc/plugin/physics/src/plugin/PhysicsPlugin.cppsrc/plugin/physics/src/resource/BodyEntityMap.hppsrc/plugin/physics/src/system/RigidBodySystem.cppsrc/plugin/physics/src/system/SoftBodySystem.cppsrc/plugin/physics/src/utils/BiMap.hppsrc/plugin/physics/tests/BodyEntityMapTest.cppsrc/plugin/physics/tests/BodyEntitySyncTest.cpptools/ai/copilot/copilot-commit-message-instructions.md
| namespace Physics::Resource { | ||
| using BodyEntityMap = Utils::BiMap<Engine::EntityId, JPH::BodyID>; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does Jolt Physics JPH::BodyID have std::hash specialization?
💡 Result:
No, Jolt Physics JPH::BodyID does not have a std::hash specialization.
Citations:
- 1: https://jrouwe.github.io/JoltPhysics/class_body_i_d.html
- 2: https://jrouwe.github.io/JoltPhysicsDocs/5.0.0/_body_i_d_8h.html
- 3: https://jrouwe.github.io/JoltPhysicsDocs/5.0.0/namespacestd.html
JPH::BodyID lacks std::hash specialization and will cause compilation failure.
The BiMap template requires both key types to be hashable. While Engine::EntityId now has a std::hash specialization, JPH::BodyID does not provide one. This will cause compilation errors when attempting to instantiate the BodyEntityMap type alias, as the underlying std::unordered_map cannot use JPH::BodyID without a hash function. A custom std::hash specialization for JPH::BodyID must be implemented or an alternative container type should be used.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/plugin/physics/src/resource/BodyEntityMap.hpp` around lines 12 - 13,
BodyEntityMap instantiation fails because JPH::BodyID lacks a std::hash
specialization required by Utils::BiMap; add a std::hash<JPH::BodyID>
specialization that converts the BodyID to a stable integral value (e.g., call
the BodyID accessor that returns its raw/index value or cast to its underlying
integral type) and returns std::hash<std::underlying_type_t<...>> applied to
that value; place the specialization in namespace std so unordered_map can use
it and then BodyEntityMap (aliasing Utils::BiMap<Engine::EntityId, JPH::BodyID>)
will compile.
| entity.AddComponent<Component::RigidBodyInternal>(bodyID); | ||
| core->GetResource<Resource::BodyEntityMap>().Add(entity, bodyID); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check BiMap::Add implementation to see if it handles duplicate insertions
ast-grep --pattern $'void Add($_, $_) {
$$$
}'
# Also search for Add method in BiMap
rg -n -A 10 'void Add' --glob '*BiMap*'Repository: EngineSquared/EngineSquared
Length of output: 946
Remove redundant BodyEntityMap::Add() call from OnRigidBodyConstruct.
The Add() call at line 320 is redundant because it will be immediately followed by an identical Add() call in OnRigidBodyInternalConstruct (triggered when the RigidBodyInternal component is created at line 319). While BiMap::Add uses insert_or_assign and is idempotent (safe to call with duplicate keys), the sequential calls are unnecessary. Remove line 320 and rely on OnRigidBodyInternalConstruct to handle the mapping.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/plugin/physics/src/system/RigidBodySystem.cpp` around lines 319 - 320,
Remove the redundant call to BodyEntityMap::Add in OnRigidBodyConstruct: when
you call entity.AddComponent<Component::RigidBodyInternal>(bodyID) the
OnRigidBodyInternalConstruct handler will perform the same
core->GetResource<Resource::BodyEntityMap>().Add(entity, bodyID) mapping, so
delete the explicit Add() line after AddComponent. Update OnRigidBodyConstruct
to only add the RigidBodyInternal component and rely on
OnRigidBodyInternalConstruct (and the existing BiMap::Add behavior) to insert
the mapping.
| Log::Warn("SoftBodyInternal has invalid BodyID, skipping body removal from BodyEntityMap"); | ||
| return; |
There was a problem hiding this comment.
Incorrect log message - says "removal" in construct handler.
The warning message says "skipping body removal" but this is OnSoftBodyInternalConstruct, so it should say "skipping body addition".
📝 Suggested fix
if (!internalComponent.IsValid())
{
- Log::Warn("SoftBodyInternal has invalid BodyID, skipping body removal from BodyEntityMap");
+ Log::Warn("SoftBodyInternal has invalid BodyID, skipping body addition to BodyEntityMap");
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Log::Warn("SoftBodyInternal has invalid BodyID, skipping body removal from BodyEntityMap"); | |
| return; | |
| Log::Warn("SoftBodyInternal has invalid BodyID, skipping body addition to BodyEntityMap"); | |
| return; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/plugin/physics/src/system/SoftBodySystem.cpp` around lines 471 - 472, The
warning in OnSoftBodyInternalConstruct incorrectly says "skipping body removal"
for SoftBodyInternal; update the Log::Warn call in the
OnSoftBodyInternalConstruct handler to state "skipping body addition to
BodyEntityMap" (or similar) so it accurately reflects the construct path and
mentions SoftBodyInternal and BodyEntityMap for clarity; keep the early return
behavior unchanged.
…gidBody and SoftBody components
There was a problem hiding this comment.
Pull request overview
This PR moves the Jolt BodyID ↔ Engine::EntityId association into the physics plugin itself by introducing an internal BodyEntityMap resource backed by a new bidirectional BiMap utility, and wiring map updates into rigid/soft body lifecycle hooks.
Changes:
- Added
Physics::Utils::BiMap<TLeft, TRight>andPhysics::Resource::BodyEntityMap(BiMap alias) for bidirectional lookup. - Physics plugin now auto-registers
BodyEntityMapand updates it fromRigidBodySystem/SoftBodySystemcallbacks. - Added
Engine::Entity::Kill(),std::hash<Engine::EntityId>, and new unit tests for mapping + sync.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/ai/copilot/copilot-commit-message-instructions.md | Adds commit message convention documentation. |
| src/plugin/physics/tests/BodyEntitySyncTest.cpp | New tests validating plugin-driven map synchronization. |
| src/plugin/physics/tests/BodyEntityMapTest.cpp | New unit tests for BodyEntityMap behavior. |
| src/plugin/physics/src/utils/BiMap.hpp | Introduces generic bidirectional map used by BodyEntityMap. |
| src/plugin/physics/src/system/SoftBodySystem.cpp | Updates soft body lifecycle to sync BodyEntityMap. |
| src/plugin/physics/src/system/RigidBodySystem.cpp | Updates rigid body lifecycle to sync BodyEntityMap. |
| src/plugin/physics/src/resource/BodyEntityMap.hpp | Defines the BodyEntityMap resource alias. |
| src/plugin/physics/src/plugin/PhysicsPlugin.cpp | Registers BodyEntityMap as a built-in physics resource. |
| src/plugin/physics/src/Physics.hpp | Exposes new resource/utility in physics umbrella header. |
| src/engine/src/entity/EntityId.hpp | Adds std::hash<Engine::EntityId> for unordered_map usage. |
| src/engine/src/entity/Entity.hpp | Adds Entity::Kill() API. |
| src/engine/src/entity/Entity.cpp | Implements Entity::Kill(). |
Comments suppressed due to low confidence (1)
src/plugin/physics/src/system/SoftBodySystem.cpp:340
- corePtr is dereferenced to construct Engine::Entity before checking for nullptr. If the registry context ever contains a null Core* (or is otherwise misconfigured), this will crash before the intended error handling. Check corePtr before using *corePtr.
auto *corePtr = registry.ctx().get<Engine::Core *>();
Engine::Entity entity{*corePtr, entityId};
if (!corePtr)
{
Log::Error("Cannot create SoftBody: Engine::Core not available");
return;
}
auto &core = *corePtr;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/plugin/physics/src/system/RigidBodySystem.cpp (1)
382-383: Map removal is duplicated across destroy handlers.
OnRigidBodyDestroyremoves the BodyID, thenentity.RemoveComponent<Component::RigidBodyInternal>()triggersOnRigidBodyInternalDestroy, which removes it again. Keep one path to reduce redundant work and lifecycle coupling.Also applies to: 391-405
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/RigidBodySystem.cpp` around lines 382 - 383, OnRigidBodyDestroy and OnRigidBodyInternalDestroy both call core->GetResource<Resource::BodyEntityMap>().Remove(internalComponent->bodyID), causing duplicated removal; pick a single owner of that responsibility (suggest removing the Remove call from OnRigidBodyDestroy and let removal happen in OnRigidBodyInternalDestroy which runs when entity.RemoveComponent<Component::RigidBodyInternal>() is invoked) and update comments to document the ownership; ensure no other code paths rely on the removed line and apply the same change for the other duplicated block (the code around the 391-405 section) so BodyEntityMap::Remove(bodyID) is only called once per lifecycle.src/plugin/physics/src/system/SoftBodySystem.cpp (2)
468-468: Avoid copyingSoftBodyInternalin hook callbacks.Use a const reference to avoid copying the component payload.
🧩 Proposed fix
- auto internalComponent = entity.GetComponents<Component::SoftBodyInternal>(); + const auto &internalComponent = entity.GetComponents<Component::SoftBodyInternal>(); ... - auto internalComponent = entity.GetComponents<Component::SoftBodyInternal>(); + const auto &internalComponent = entity.GetComponents<Component::SoftBodyInternal>();Also applies to: 518-518
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/SoftBodySystem.cpp` at line 468, The code is copying the Component::SoftBodyInternal payload by using auto internalComponent = entity.GetComponents<Component::SoftBodyInternal>(); in SoftBodySystem.cpp; change the declaration to bind a const reference (e.g., const auto& internalComponent or const Component::SoftBodyInternal& internalComponent) so the hook callbacks don't copy the component data, and apply the same change to the other occurrence around the 518 call site; ensure you still respect the component's lifetime when using the reference.
447-447: BodyEntityMap is updated twice via both direct calls and internal hooks.
OnSoftBodyInternalConstruct/OnSoftBodyInternalDestroyalready synchronize the map, so explicit Add/Remove in soft-body construct/destroy is redundant.♻️ Proposed simplification
- core.GetResource<Resource::BodyEntityMap>().Add(entity, bodyID); ... - core.GetResource<Resource::BodyEntityMap>().Remove(internal->bodyID); entity.RemoveComponent<Component::SoftBodyInternal>();Also applies to: 502-503, 540-542
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/system/SoftBodySystem.cpp` at line 447, Remove the redundant explicit BodyEntityMap updates in the soft-body construct/destroy handlers: delete the core.GetResource<Resource::BodyEntityMap>().Add(...) and .Remove(...) calls that appear alongside the soft-body construction/destruction code and rely instead on the existing OnSoftBodyInternalConstruct and OnSoftBodyInternalDestroy hooks to keep BodyEntityMap in sync; specifically remove the Add at the construct site and the corresponding Remove(s) at the destroy sites so only the internal hook methods manage the map.src/plugin/physics/tests/BodyEntityMapTest.cpp (1)
79-91: Add a reverse-update case (same BodyID, different EntityId).Please also test
Add(EntityA, BodyX)followed byAdd(EntityB, BodyX)to validate old left-key eviction on right-key reassignment.🧪 Proposed extension
TEST(PhysicsPlugin, BodyEntityMapUpdate) { Physics::Resource::BodyEntityMap map; map.Add(Engine::EntityId{1}, JPH::BodyID{10}); EXPECT_EQ(map.Get(Engine::EntityId{1}), JPH::BodyID{10}); map.Add(Engine::EntityId{1}, JPH::BodyID{20}); EXPECT_EQ(map.Size(), 1); EXPECT_EQ(map.Get(Engine::EntityId{1}), JPH::BodyID{20}); EXPECT_FALSE(map.Contains(JPH::BodyID{10})); + + map.Add(Engine::EntityId{2}, JPH::BodyID{20}); + EXPECT_EQ(map.Size(), 1); + EXPECT_FALSE(map.Contains(Engine::EntityId{1})); + EXPECT_EQ(map.Get(JPH::BodyID{20}), Engine::EntityId{2}); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/tests/BodyEntityMapTest.cpp` around lines 79 - 91, Add a new test case in BodyEntityMapTest that exercises the reverse-update: call map.Add(Engine::EntityId{1}, JPH::BodyID{10}) then map.Add(Engine::EntityId{2}, JPH::BodyID{10}) and assert the old left-key was evicted and the right-key reassigned; specifically check map.Size() == 1, map.Get(Engine::EntityId{2}) == JPH::BodyID{10}, map no longer returns the old mapping for Engine::EntityId{1} (e.g. Get(Engine::EntityId{1}) is not JPH::BodyID{10} or is invalid), and map.Contains(JPH::BodyID{10}) is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/plugin/physics/src/system/RigidBodySystem.cpp`:
- Around line 417-420: The RigidBody event hookups
(registry.on_construct<Component::RigidBody>().connect<&OnRigidBodyConstruct>(),
registry.on_construct<Component::RigidBodyInternal>().connect<&OnRigidBodyInternalConstruct>(),
registry.on_destroy<Component::RigidBody>().connect<&OnRigidBodyDestroy>(),
registry.on_destroy<Component::RigidBodyInternal>().connect<&OnRigidBodyInternalDestroy>())
need matching disconnects on plugin shutdown to avoid callbacks running during
teardown; add a ShutdownRigidBodySystem function that disconnects those four
connections (mirror the pattern used for soft body) and ensure
ShutdownRigidBodySystem is registered in the plugin shutdown schedule so it runs
before physics resources are destroyed.
In `@src/plugin/physics/src/system/SoftBodySystem.cpp`:
- Around line 332-337: The code dereferences corePtr when constructing
Engine::Entity entity{*corePtr, entityId} before checking for null, which can
crash; move the null check for corePtr (result of
registry.ctx().get<Engine::Core *()> ) before any use of corePtr, and if null
log the error via Log::Error("Cannot create SoftBody: Engine::Core not
available") and return/abort the soft-body creation path instead of constructing
Engine::Entity; update any early-return or error handling in the surrounding
function to match this control flow.
In `@tools/ai/copilot/copilot-commit-message-instructions.md`:
- Line 15: Update the sentence that currently reads "The `<type>` and
`<summary>` fields are mandatory, the `(<scope>)` field is optional." to use the
canonical placeholder `<short summary>` instead of `<summary>` so it matches the
header structure; ensure the line now reads "The `<type>` and `<short summary>`
fields are mandatory, the `(<scope>)` field is optional." and verify any other
occurrences of `<summary>` in this document are replaced with `<short summary>`
for consistency.
---
Nitpick comments:
In `@src/plugin/physics/src/system/RigidBodySystem.cpp`:
- Around line 382-383: OnRigidBodyDestroy and OnRigidBodyInternalDestroy both
call
core->GetResource<Resource::BodyEntityMap>().Remove(internalComponent->bodyID),
causing duplicated removal; pick a single owner of that responsibility (suggest
removing the Remove call from OnRigidBodyDestroy and let removal happen in
OnRigidBodyInternalDestroy which runs when
entity.RemoveComponent<Component::RigidBodyInternal>() is invoked) and update
comments to document the ownership; ensure no other code paths rely on the
removed line and apply the same change for the other duplicated block (the code
around the 391-405 section) so BodyEntityMap::Remove(bodyID) is only called once
per lifecycle.
In `@src/plugin/physics/src/system/SoftBodySystem.cpp`:
- Line 468: The code is copying the Component::SoftBodyInternal payload by using
auto internalComponent = entity.GetComponents<Component::SoftBodyInternal>(); in
SoftBodySystem.cpp; change the declaration to bind a const reference (e.g.,
const auto& internalComponent or const Component::SoftBodyInternal&
internalComponent) so the hook callbacks don't copy the component data, and
apply the same change to the other occurrence around the 518 call site; ensure
you still respect the component's lifetime when using the reference.
- Line 447: Remove the redundant explicit BodyEntityMap updates in the soft-body
construct/destroy handlers: delete the
core.GetResource<Resource::BodyEntityMap>().Add(...) and .Remove(...) calls that
appear alongside the soft-body construction/destruction code and rely instead on
the existing OnSoftBodyInternalConstruct and OnSoftBodyInternalDestroy hooks to
keep BodyEntityMap in sync; specifically remove the Add at the construct site
and the corresponding Remove(s) at the destroy sites so only the internal hook
methods manage the map.
In `@src/plugin/physics/tests/BodyEntityMapTest.cpp`:
- Around line 79-91: Add a new test case in BodyEntityMapTest that exercises the
reverse-update: call map.Add(Engine::EntityId{1}, JPH::BodyID{10}) then
map.Add(Engine::EntityId{2}, JPH::BodyID{10}) and assert the old left-key was
evicted and the right-key reassigned; specifically check map.Size() == 1,
map.Get(Engine::EntityId{2}) == JPH::BodyID{10}, map no longer returns the old
mapping for Engine::EntityId{1} (e.g. Get(Engine::EntityId{1}) is not
JPH::BodyID{10} or is invalid), and map.Contains(JPH::BodyID{10}) is true.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 34785713-792d-4e3c-bd22-e3e121c637ff
📒 Files selected for processing (6)
src/plugin/physics/src/system/RigidBodySystem.cppsrc/plugin/physics/src/system/SoftBodySystem.cppsrc/plugin/physics/src/utils/BiMap.hppsrc/plugin/physics/tests/BodyEntityMapTest.cppsrc/plugin/physics/tests/BodyEntitySyncTest.cpptools/ai/copilot/copilot-commit-message-instructions.md
🚧 Files skipped from review as they are similar to previous changes (1)
- src/plugin/physics/tests/BodyEntitySyncTest.cpp
|
@Miou-zora I've opened a new pull request, #501, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
…idation (#501) `BiMap` stored raw pointers to keys inside each `std::unordered_map` as cross-references. Any insert or erase triggering a rehash invalidated those pointers, causing use-after-free bugs on lookup and removal. ## Changes - **`BiMap.hpp`**: Replace pointer-based cross-reference maps with two independent value-copying maps: - `std::unordered_map<TLeft, TRight> _leftToRight` - `std::unordered_map<TRight, TLeft> _rightToLeft` - Update `Add`, `Remove`, `Get`, and `Contains` to work with stored values directly instead of dereferencing pointers. **Before:** ```cpp std::unordered_map<TLeft, const TRight *> _leftToRight; // pointer into _rightToLeft std::unordered_map<TRight, const TLeft *> _rightToLeft; // pointer into _leftToRight auto aitr = this->_leftToRight.insert_or_assign(left, nullptr); auto bp = &((*aitr.first).first); // raw pointer to key — invalidated on rehash auto bitr = this->_rightToLeft.insert_or_assign(right, bp).first; auto ap = &((*bitr).first); (*aitr.first).second = ap; ``` **After:** ```cpp std::unordered_map<TLeft, TRight> _leftToRight; // value copy std::unordered_map<TRight, TLeft> _rightToLeft; // value copy this->_leftToRight.insert_or_assign(left, right); this->_rightToLeft.insert_or_assign(right, left); ``` <!-- START COPILOT CODING AGENT TIPS --> --- 💬 Send tasks to Copilot coding agent from [Slack](https://gh.io/cca-slack-docs) and [Teams](https://gh.io/cca-teams-docs) to turn conversations into code. Copilot posts an update in your thread when it's finished. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Miou-zora <[email protected]>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/plugin/physics/src/utils/BiMap.hpp (1)
49-49: Usestd::size_tforSize()to avoid truncation.Line 49 narrows from container
size()tostd::uint32_t. Returningstd::size_tis safer and matches STL conventions.Suggested change
- std::uint32_t Size() const { return static_cast<std::uint32_t>(this->_leftToRight.size()); } + std::size_t Size() const { return this->_leftToRight.size(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugin/physics/src/utils/BiMap.hpp` at line 49, The Size() accessor in class BiMap.hpp currently returns std::uint32_t causing potential truncation; change its return type to std::size_t and return this->_leftToRight.size() directly (no narrowing cast). Update the Size() declaration and its definition (function named Size) to use std::size_t, and then adjust any call-sites that assumed a uint32_t return type to handle std::size_t if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/plugin/physics/src/utils/BiMap.hpp`:
- Line 49: The Size() accessor in class BiMap.hpp currently returns
std::uint32_t causing potential truncation; change its return type to
std::size_t and return this->_leftToRight.size() directly (no narrowing cast).
Update the Size() declaration and its definition (function named Size) to use
std::size_t, and then adjust any call-sites that assumed a uint32_t return type
to handle std::size_t if necessary.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: be8cf709-ec4b-45c3-a909-d9d3aea3b99e
📒 Files selected for processing (3)
src/plugin/physics/src/system/RigidBodySystem.cppsrc/plugin/physics/src/system/SoftBodySystem.cppsrc/plugin/physics/src/utils/BiMap.hpp
🚧 Files skipped from review as they are similar to previous changes (2)
- src/plugin/physics/src/system/SoftBodySystem.cpp
- src/plugin/physics/src/system/RigidBodySystem.cpp
…or vehicle movement and synchronization
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/plugin/physics/tests/PhysicsTest.cpp`:
- Around line 73-78: The assertion for collisionRemoved runs too early inside
the TestScheduler callback; add a second phase that explicitly separates or
destroys the cube and then checks collisionRemoved on a later tick (after the
separation/destruction and after the scheduler processes that change).
Concretely, modify the TestScheduler (registered via
c.RegisterSystem<TestScheduler>) to: 1) keep the existing first-phase
assertions, 2) schedule a follow-up tick or callback that either move the cube's
Transform away or call the cube destruction/removal path, and 3) assert
EXPECT_TRUE(collisionRemoved) only in that follow-up phase (or after
Engine::Scheduler::Shutdown has been processed) so the removal path is
guaranteed to have run.
In `@src/plugin/physics/tests/VehicleForwardMovementTest.cpp`:
- Around line 81-95: The scheduled lambda uses ASSERT_NE for the transform and
controller lookups which returns from the lambda without setting firstStepMade
or stopping the Engine::Core, causing the test to hang; replace those ASSERT_NE
checks with explicit null checks on
vehicle.TryGetComponent<Object::Component::Transform>() and
vehicle.TryGetComponent<Physics::Component::VehicleController>() that, on
failure, call core.Stop() and fail the test (e.g., GTEST_FAIL or FAIL()) before
returning, ensuring firstStepMade is only set after successful setup and
controller->SetForward(1.0f) is only called when components are valid.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 81146391-c85e-4355-86db-5f1cbe873cd0
📒 Files selected for processing (9)
src/plugin/physics/src/plugin/PhysicsPlugin.cppsrc/plugin/physics/src/system/RigidBodySystem.cppsrc/plugin/physics/src/system/RigidBodySystem.hppsrc/plugin/physics/src/system/SoftBodySystem.cppsrc/plugin/physics/tests/PhysicsTest.cppsrc/plugin/physics/tests/VehicleForwardMovementTest.cppsrc/plugin/physics/tests/VehicleGravityDropTest.cppsrc/plugin/physics/tests/VehicleWheelTransformSyncTest.cpptools/ai/copilot/copilot-commit-message-instructions.md
✅ Files skipped from review due to trivial changes (1)
- tools/ai/copilot/copilot-commit-message-instructions.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/plugin/physics/src/system/RigidBodySystem.cpp
- src/plugin/physics/src/system/SoftBodySystem.cpp
| c.RegisterSystem<TestScheduler>([&](Engine::Core &core) { | ||
| static bool firstStepMade = false; | ||
| if (!firstStepMade) | ||
| { | ||
| // Record starting position | ||
| auto transform = vehicle.TryGetComponent<Object::Component::Transform>(); | ||
| ASSERT_NE(transform, nullptr); | ||
| startPos = transform->GetPosition(); | ||
|
|
||
| // Apply forward input | ||
| auto controller = vehicle.TryGetComponent<Physics::Component::VehicleController>(); | ||
| ASSERT_NE(controller, nullptr); | ||
| controller->SetForward(1.0f); // Full throttle | ||
| firstStepMade = true; | ||
| } |
There was a problem hiding this comment.
Fatal assertions here can hang Engine::Core::RunCore().
If either lookup fails, ASSERT_NE returns from the scheduled lambda, firstStepMade stays false, and the test never reaches core.Stop(). That turns a normal failure into a stuck test process.
🛠️ Fail fast and stop the core on setup errors
- c.RegisterSystem<TestScheduler>([&](Engine::Core &core) {
- static bool firstStepMade = false;
+ bool firstStepMade = false;
+ c.RegisterSystem<TestScheduler>([&](Engine::Core &core) {
if (!firstStepMade)
{
- // Record starting position
auto transform = vehicle.TryGetComponent<Object::Component::Transform>();
- ASSERT_NE(transform, nullptr);
+ if (transform == nullptr)
+ {
+ ADD_FAILURE() << "Vehicle transform missing";
+ core.Stop();
+ return;
+ }
startPos = transform->GetPosition();
- // Apply forward input
auto controller = vehicle.TryGetComponent<Physics::Component::VehicleController>();
- ASSERT_NE(controller, nullptr);
+ if (controller == nullptr)
+ {
+ ADD_FAILURE() << "Vehicle controller missing";
+ core.Stop();
+ return;
+ }
controller->SetForward(1.0f); // Full throttle
firstStepMade = true;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/plugin/physics/tests/VehicleForwardMovementTest.cpp` around lines 81 -
95, The scheduled lambda uses ASSERT_NE for the transform and controller lookups
which returns from the lambda without setting firstStepMade or stopping the
Engine::Core, causing the test to hang; replace those ASSERT_NE checks with
explicit null checks on vehicle.TryGetComponent<Object::Component::Transform>()
and vehicle.TryGetComponent<Physics::Component::VehicleController>() that, on
failure, call core.Stop() and fail the test (e.g., GTEST_FAIL or FAIL()) before
returning, ensuring firstStepMade is only set after successful setup and
controller->SetForward(1.0f) is only called when components are valid.



Pull Request
Description
Previously, every project using the physics plugin had to manually maintain a
PhysicsBodyToEntityMapresource — a bidirectional map between JoltBodyIDs and engineEntityhandles. This was error-prone boilerplate that didn't belong in user code.This PR internalizes the
BodyID↔Entitymapping directly inside the Physics plugin. ABodyEntityMapresource (backed by a new genericBiMap<TLeft, TRight>utility) is now automatically registered byPhysicsPluginand kept in sync by theRigidBodySystemandSoftBodySystemon body creation and destruction. Projects using physics no longer need to declare or synchronize this map themselves.Related Issues
Fixes #496
Type of Change
Changes Made
Utils::BiMap<TLeft, TRight>: a generic bidirectional map backed by twounordered_maps, supportingAdd,Remove(by either key),Get, andContains.Resource::BodyEntityMapas a type alias forBiMap<Engine::EntityId, JPH::BodyID>.BodyEntityMapas a built-in resource inPhysicsPlugin::Bind().RigidBodySystemandSoftBodySystemto automatically insert/remove entries inBodyEntityMapon body construct/destroy callbacks.std::hash<Engine::EntityId>specialization required for use inunordered_map.Entity::Kill()method and updated internal systems to use the higher-levelEngine::EntityAPI instead of raw registry calls.BodyEntityMapTestandBodyEntitySyncTest.Testing
xmake test)clang-format)Test Environment
Screenshots/Videos (if applicable)
None
Documentation
Checklist
Breaking Changes
None. The
BodyEntityMapresource is now registered automatically by the plugin — projects that previously registered their ownPhysicsBodyToEntityMapshould remove it to avoid duplication, but it is not required immediately.Additional Notes
This was identified via a
// TODOaudit of the ES-Factoria demo project. Validation step: removePhysicsBodyToEntityMapfrom ES-Factoria and confirm everything still works viaBodyEntityMap.Summary by CodeRabbit
New Features
Refactor
Tests
Documentation