diff --git a/Library/include/playrho/d2/AabbTreeWorld.hpp b/Library/include/playrho/d2/AabbTreeWorld.hpp index 3795cc1ed..8fb06d7f0 100644 --- a/Library/include/playrho/d2/AabbTreeWorld.hpp +++ b/Library/include/playrho/d2/AabbTreeWorld.hpp @@ -64,10 +64,13 @@ #include #include +#include #include #include #include #include +#include +#include #include #include @@ -82,9 +85,6 @@ enum class BodyType; namespace playrho::d2 { -class Body; -class Joint; -class Shape; class Manifold; class ContactImpulsesList; class AabbTreeWorld; @@ -416,9 +416,12 @@ void SetJoint(AabbTreeWorld& world, JointID id, Joint def); void Destroy(AabbTreeWorld& world, JointID id); /// @brief Gets whether the given identifier is to a joint that's been destroyed. -/// @note Complexity is at most O(n) where n is the number of elements free. +/// @note Complexity is O(1). /// @see Destroy(AabbTreeWorld& world, JointID). -bool IsDestroyed(const AabbTreeWorld& world, JointID id) noexcept; +inline auto IsDestroyed(const AabbTreeWorld& world, JointID id) -> bool +{ + return IsDestroyed(GetJoint(world, id)); +} /// @} @@ -466,9 +469,12 @@ void SetShape(AabbTreeWorld& world, ShapeID id, Shape def); void Destroy(AabbTreeWorld& world, ShapeID id); /// @brief Gets whether the given identifier is to a shape that's been destroyed. -/// @note Complexity is at most O(n) where n is the number of elements free. +/// @note Complexity is O(1). /// @see Destroy(AabbTreeWorld& world, ShapeID). -bool IsDestroyed(const AabbTreeWorld& world, ShapeID id) noexcept; +inline auto IsDestroyed(const AabbTreeWorld& world, ShapeID id) -> bool +{ + return IsDestroyed(GetShape(world, id)); +} /// @} @@ -627,7 +633,6 @@ class AabbTreeWorld { friend const Joint& GetJoint(const AabbTreeWorld& world, JointID id); friend void SetJoint(AabbTreeWorld& world, JointID id, Joint def); friend void Destroy(AabbTreeWorld& world, JointID id); - friend bool IsDestroyed(const AabbTreeWorld& world, JointID id) noexcept; // Shape friend functions... friend ShapeCounter GetShapeRange(const AabbTreeWorld& world) noexcept; @@ -635,7 +640,6 @@ class AabbTreeWorld { friend const Shape& GetShape(const AabbTreeWorld& world, ShapeID id); friend void SetShape(AabbTreeWorld& world, ShapeID id, Shape def); friend void Destroy(AabbTreeWorld& world, ShapeID id); - friend bool IsDestroyed(const AabbTreeWorld& world, ShapeID id) noexcept; // Contact friend functions... friend ContactCounter GetContactRange(const AabbTreeWorld& world) noexcept; diff --git a/Library/include/playrho/d2/Joint.hpp b/Library/include/playrho/d2/Joint.hpp index 3e15d9107..2661ba870 100644 --- a/Library/include/playrho/d2/Joint.hpp +++ b/Library/include/playrho/d2/Joint.hpp @@ -457,6 +457,13 @@ inline std::add_pointer_t TypeCast(Joint* value) noexcept return nullptr; } +/// @brief Gets whether the given entity is in the is-destroyed state. +/// @relatedalso Joint +inline auto IsDestroyed(const Joint &object) noexcept -> bool +{ + return !object.has_value(); +} + /// Get the anchor point on body-A in local coordinates. /// @relatedalso Joint Length2 GetLocalAnchorA(const Joint& object); diff --git a/Library/include/playrho/d2/Shape.hpp b/Library/include/playrho/d2/Shape.hpp index b673425c8..9e74a82b0 100644 --- a/Library/include/playrho/d2/Shape.hpp +++ b/Library/include/playrho/d2/Shape.hpp @@ -497,6 +497,13 @@ inline T TypeCast(const Shape& value) return static_cast(*tmp); } +/// @brief Gets whether the given entity is in the is-destroyed state. +/// @relatedalso Shape +inline auto IsDestroyed(const Shape &value) noexcept -> bool +{ + return !value.has_value(); +} + /// @brief Whether contact calculations should be performed between the two instances. /// @return true if contact calculations should be performed between these /// two instances; false otherwise. diff --git a/Library/source/playrho/d2/AabbTreeWorld.cpp b/Library/source/playrho/d2/AabbTreeWorld.cpp index 625076c18..01c38e8d7 100644 --- a/Library/source/playrho/d2/AabbTreeWorld.cpp +++ b/Library/source/playrho/d2/AabbTreeWorld.cpp @@ -22,7 +22,6 @@ #include #include // for assert #include // for std::size_t -#include // for std::uint32_t #include // for std::throw_with_nested #include #include // for std::next @@ -1112,14 +1111,17 @@ void SetJoint(AabbTreeWorld& world, JointID id, Joint def) } // Validate the references... auto &joint = At(world.m_jointBuffer, id, noSuchJointMsg); + if (!joint.has_value()) { + throw WasDestroyed{id, idIsDestroyedMsg}; + } if (const auto bodyId = GetBodyA(def); bodyId != InvalidBodyID) { GetBody(world, bodyId); } if (const auto bodyId = GetBodyB(def); bodyId != InvalidBodyID) { GetBody(world, bodyId); } - if (world.m_jointBuffer.FindFree(to_underlying(id))) { - throw WasDestroyed{id, idIsDestroyedMsg}; + if (!def.has_value()) { + throw WasDestroyed{def, "cannot be empty"}; } world.Remove(id); joint = std::move(def); @@ -1134,6 +1136,9 @@ JointID CreateJoint(AabbTreeWorld& world, Joint def) if (size(world.m_joints) >= MaxJoints) { throw LengthError("CreateJoint: operation would exceed MaxJoints"); } + if (!def.has_value()) { + throw WasDestroyed{def, "cannot be empty"}; + } // Validate the referenced bodies... if (const auto bodyId = GetBodyA(def); bodyId != InvalidBodyID) { GetBody(world, bodyId); @@ -1220,11 +1225,6 @@ void Destroy(AabbTreeWorld& world, JointID id) } } -bool IsDestroyed(const AabbTreeWorld& world, JointID id) noexcept -{ - return world.m_jointBuffer.FindFree(to_underlying(id)); -} - ShapeCounter GetShapeRange(const AabbTreeWorld& world) noexcept { return static_cast(size(world.m_shapeBuffer)); @@ -1232,6 +1232,9 @@ ShapeCounter GetShapeRange(const AabbTreeWorld& world) noexcept ShapeID CreateShape(AabbTreeWorld& world, Shape def) { + if (!def.has_value()) { + throw WasDestroyed{def, "cannot be empty"}; + } const auto vertexRadius = GetVertexRadiusInterval(world); const auto childCount = GetChildCount(def); for (auto i = ChildCounter{0}; i < childCount; ++i) { @@ -1272,11 +1275,6 @@ void Destroy(AabbTreeWorld& world, ShapeID id) world.m_shapeBuffer.Free(to_underlying(id)); } -bool IsDestroyed(const AabbTreeWorld& world, ShapeID id) noexcept -{ - return world.m_shapeBuffer.FindFree(to_underlying(id)); -} - const Shape& GetShape(const AabbTreeWorld& world, ShapeID id) { return At(world.m_shapeBuffer, id, noSuchShapeMsg); @@ -1288,9 +1286,12 @@ void SetShape(AabbTreeWorld& world, ShapeID id, Shape def) // NOLINT(readability throw WrongState(worldIsLockedMsg); } auto& shape = At(world.m_shapeBuffer, id, noSuchShapeMsg); - if (world.m_shapeBuffer.FindFree(to_underlying(id))) { + if (!shape.has_value()) { throw WasDestroyed{id, idIsDestroyedMsg}; } + if (!def.has_value()) { + throw WasDestroyed{def, "cannot be empty"}; + } const auto geometryChanged = IsGeomChanged(shape, def); for (auto&& b: world.m_bodyBuffer) { if (!Find(b.GetShapes(), id)) { diff --git a/UnitTests/AabbTreeWorld.cpp b/UnitTests/AabbTreeWorld.cpp index cb0e45319..a258c83d7 100644 --- a/UnitTests/AabbTreeWorld.cpp +++ b/UnitTests/AabbTreeWorld.cpp @@ -210,7 +210,7 @@ TEST(AabbTreeWorld, Clear) EXPECT_EQ(GetBodies(world).size(), std::size_t(0)); EXPECT_EQ(GetJoints(world).size(), std::size_t(0)); EXPECT_EQ(GetJointRange(world), 0u); - EXPECT_FALSE(IsDestroyed(world, JointID{0u})); // out-of-range so not destroyed + EXPECT_THROW(IsDestroyed(world, JointID{0u}), OutOfRange); EXPECT_EQ(shapeListener.ids.size(), std::size_t(1)); @@ -1016,13 +1016,20 @@ TEST(AabbTreeWorld, AttachDetach) ASSERT_EQ(GetShapes(world, BodyID{0}).size(), 0u); } -TEST(AabbTreeWorld, SetShapeThrowsWithOutOfRangeID) +TEST(AabbTreeWorld, SetShapeThrowsWithEmpty) { auto world = AabbTreeWorld{}; ASSERT_EQ(GetShapeRange(world), 0u); EXPECT_THROW(SetShape(world, ShapeID(0), Shape{}), OutOfRange); } +TEST(AabbTreeWorld, SetShapeThrowsWithOutOfRangeID) +{ + auto world = AabbTreeWorld{}; + ASSERT_EQ(GetShapeRange(world), 0u); + EXPECT_THROW(SetShape(world, ShapeID(0), Shape{EdgeShapeConf{}}), OutOfRange); +} + TEST(AabbTreeWorld, CreateBodyThrowsWithOutOfRangeShapeID) { auto world = AabbTreeWorld{}; @@ -1143,11 +1150,17 @@ TEST(AabbTreeWorld, SetShapeWithGeometryChange) EXPECT_EQ(size(GetProxies(world, bodyId)), 3u); } -TEST(AabbTreeWorld, SetFreedShapeThrows) +TEST(AabbTreeWorld, CreateEmptyShapeThrows) +{ + auto world = AabbTreeWorld{}; + EXPECT_THROW(CreateShape(world, Shape()), WasDestroyed); +} + +TEST(AabbTreeWorld, SetDestroyedShapeThrows) { auto world = AabbTreeWorld{}; auto id = InvalidShapeID; - ASSERT_NO_THROW(id = CreateShape(world, Shape())); + ASSERT_NO_THROW(id = CreateShape(world, Shape(EdgeShapeConf{}))); ASSERT_NO_THROW(Destroy(world, id)); ASSERT_TRUE(IsDestroyed(world, id)); EXPECT_THROW(SetShape(world, id, Shape()), WasDestroyed); @@ -1162,15 +1175,29 @@ TEST(AabbTreeWorld, SetFreedBodyThrows) EXPECT_THROW(SetBody(world, id, Body()), WasDestroyed); } -TEST(AabbTreeWorld, SetFreedJointThrows) +TEST(AabbTreeWorld, CreateEmptyJointThrows) +{ + auto world = AabbTreeWorld{}; + EXPECT_THROW(CreateJoint(world, Joint()), WasDestroyed); +} + +TEST(AabbTreeWorld, SetDestroyedJointThrows) { auto world = AabbTreeWorld{}; auto id = InvalidJointID; - ASSERT_NO_THROW(id = CreateJoint(world, Joint())); + ASSERT_NO_THROW(id = CreateJoint(world, Joint(DistanceJointConf{}))); ASSERT_NO_THROW(Destroy(world, id)); EXPECT_THROW(SetJoint(world, id, Joint()), WasDestroyed); } +TEST(AabbTreeWorld, SetEmptyJointThrows) +{ + auto world = AabbTreeWorld{}; + auto id = InvalidJointID; + ASSERT_NO_THROW(id = CreateJoint(world, Joint(DistanceJointConf{}))); + EXPECT_THROW(SetJoint(world, id, Joint()), WasDestroyed); +} + TEST(AabbTreeWorld, SetBodyWithShapeID) { auto world = AabbTreeWorld{};