Skip to content

Commit

Permalink
Multiple changes...
Browse files Browse the repository at this point in the history
- Makes use of empty Shape and empty Joint as is-destroyed info.
- Switches remaining IsDestroyed functions from using FindFree to using
  boolean is-destroyed info from Joint & Shape.
- Updates associated unit tests.
  • Loading branch information
louis-langholtz committed Jan 7, 2024
1 parent b7ca22a commit c06f9d3
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 29 deletions.
22 changes: 13 additions & 9 deletions Library/include/playrho/d2/AabbTreeWorld.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,13 @@
#include <playrho/pmr/PoolMemoryResource.hpp>
#include <playrho/pmr/StatsResource.hpp>

#include <playrho/d2/Body.hpp>
#include <playrho/d2/BodyConstraint.hpp>
#include <playrho/d2/ContactImpulsesFunction.hpp>
#include <playrho/d2/ContactManifoldFunction.hpp>
#include <playrho/d2/DynamicTree.hpp>
#include <playrho/d2/Joint.hpp>
#include <playrho/d2/Shape.hpp>
#include <playrho/d2/Transformation.hpp>
#include <playrho/d2/WorldConf.hpp>

Expand All @@ -82,9 +85,6 @@ enum class BodyType;

namespace playrho::d2 {

class Body;
class Joint;
class Shape;
class Manifold;
class ContactImpulsesList;
class AabbTreeWorld;
Expand Down Expand Up @@ -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));
}

/// @}

Expand Down Expand Up @@ -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));
}

/// @}

Expand Down Expand Up @@ -627,15 +633,13 @@ 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;
friend ShapeID CreateShape(AabbTreeWorld& world, Shape def);
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;
Expand Down
7 changes: 7 additions & 0 deletions Library/include/playrho/d2/Joint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,13 @@ inline std::add_pointer_t<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);
Expand Down
7 changes: 7 additions & 0 deletions Library/include/playrho/d2/Shape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,13 @@ inline T TypeCast(const Shape& value)
return static_cast<T>(*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 <code>true</code> if contact calculations should be performed between these
/// two instances; <code>false</code> otherwise.
Expand Down
29 changes: 15 additions & 14 deletions Library/source/playrho/d2/AabbTreeWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#include <algorithm>
#include <cassert> // for assert
#include <cstddef> // for std::size_t
#include <cstdint> // for std::uint32_t
#include <exception> // for std::throw_with_nested
#include <functional>
#include <iterator> // for std::next
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -1220,18 +1225,16 @@ 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<ShapeCounter>(size(world.m_shapeBuffer));
}

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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand Down
39 changes: 33 additions & 6 deletions UnitTests/AabbTreeWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<JointID>);

EXPECT_EQ(shapeListener.ids.size(), std::size_t(1));

Expand Down Expand Up @@ -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<ShapeID>);
}

TEST(AabbTreeWorld, SetShapeThrowsWithOutOfRangeID)
{
auto world = AabbTreeWorld{};
ASSERT_EQ(GetShapeRange(world), 0u);
EXPECT_THROW(SetShape(world, ShapeID(0), Shape{EdgeShapeConf{}}), OutOfRange<ShapeID>);
}

TEST(AabbTreeWorld, CreateBodyThrowsWithOutOfRangeShapeID)
{
auto world = AabbTreeWorld{};
Expand Down Expand Up @@ -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<Shape>);
}

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<ShapeID>);
Expand All @@ -1162,15 +1175,29 @@ TEST(AabbTreeWorld, SetFreedBodyThrows)
EXPECT_THROW(SetBody(world, id, Body()), WasDestroyed<BodyID>);
}

TEST(AabbTreeWorld, SetFreedJointThrows)
TEST(AabbTreeWorld, CreateEmptyJointThrows)
{
auto world = AabbTreeWorld{};
EXPECT_THROW(CreateJoint(world, Joint()), WasDestroyed<Joint>);
}

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<JointID>);
}

TEST(AabbTreeWorld, SetEmptyJointThrows)
{
auto world = AabbTreeWorld{};
auto id = InvalidJointID;
ASSERT_NO_THROW(id = CreateJoint(world, Joint(DistanceJointConf{})));
EXPECT_THROW(SetJoint(world, id, Joint()), WasDestroyed<Joint>);
}

TEST(AabbTreeWorld, SetBodyWithShapeID)
{
auto world = AabbTreeWorld{};
Expand Down

0 comments on commit c06f9d3

Please sign in to comment.