Skip to content

Commit

Permalink
Plumbs GetResourceStats through World
Browse files Browse the repository at this point in the history
- Also identified and fixes AabbTreeWorld copy/move construction w.r.t.
  resource stats collection.
  • Loading branch information
louis-langholtz committed Oct 12, 2023
1 parent fc048fd commit 352d716
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 48 deletions.
25 changes: 21 additions & 4 deletions Library/include/playrho/d2/AabbTreeWorld.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <stack>
#include <stdexcept>
#include <type_traits> // for std::is_default_constructible_v, etc.
Expand Down Expand Up @@ -146,7 +147,7 @@ void SetPostSolveContactListener(AabbTreeWorld& world, ImpulsesContactListener l
/// @{

/// @brief Gets the resource statistics of the specified world.
pmr::StatsResource::Stats GetResourceStats(const AabbTreeWorld& world) noexcept;
std::optional<pmr::StatsResource::Stats> GetResourceStats(const AabbTreeWorld& world) noexcept;

/// @brief Clears this world.
/// @note This calls the joint and shape destruction listeners (if they're set), for all
Expand Down Expand Up @@ -508,12 +509,27 @@ class AabbTreeWorld {
/// @note A lot more configurability can be had via the <code>StepConf</code>
/// data that's given to the world's <code>Step</code> function.
/// @see Step.
/// @post <code>GetResourceStats(const AabbTreeWorld&)</code> for this world returns an empty
/// value if <code>conf.doStats</code> is false, a non-empty value otherwise.
/// @post <code>GetVertexRadiusInterval(const AabbTreeWorld&)</code> for this world returns
/// <code>conf.vertexRadius</code>.
explicit AabbTreeWorld(const WorldConf& conf = WorldConf{});

/// @brief Copy constructor.
/// @detail Basically copy constructs this world as a deep copy of the given world.
/// @post <code>GetResourceStats(const AabbTreeWorld&)</code> for this world returns an empty
/// value if <code>GetResourceStats(other)</code> returns an empty value, a non-empty value
/// that's zero initialized otherwise.
/// @post <code>GetVertexRadiusInterval(const AabbTreeWorld&)</code> for this world returns
/// the same value as <code>GetVertexRadiusInterval(other)</code>.
AabbTreeWorld(const AabbTreeWorld& other);

/// @brief Move constructor.
/// @post <code>GetResourceStats(const AabbTreeWorld&)</code> for this world returns an empty
/// value if <code>GetResourceStats(other)</code> returned an empty value, a non-empty value
/// that's zero initialized otherwise.
/// @post <code>GetVertexRadiusInterval(const AabbTreeWorld&)</code> for this world returns
/// the value of <code>GetVertexRadiusInterval(other)</code> just before this call.
AabbTreeWorld(AabbTreeWorld&& other) noexcept;

/// @brief Destructor.
Expand All @@ -536,7 +552,7 @@ class AabbTreeWorld {
friend void SetPostSolveContactListener(AabbTreeWorld& world, ImpulsesContactListener listener) noexcept;

// Miscellaneous friend functions...
friend pmr::StatsResource::Stats GetResourceStats(const AabbTreeWorld& world) noexcept;
friend std::optional<pmr::StatsResource::Stats> GetResourceStats(const AabbTreeWorld& world) noexcept;
friend void Clear(AabbTreeWorld& world) noexcept;
friend StepStats Step(AabbTreeWorld& world, const StepConf& conf);
friend bool IsStepComplete(const AabbTreeWorld& world) noexcept;
Expand Down Expand Up @@ -957,9 +973,10 @@ static_assert(std::is_move_constructible_v<AabbTreeWorld>);
static_assert(!std::is_copy_assignable_v<AabbTreeWorld>);
static_assert(!std::is_move_assignable_v<AabbTreeWorld>);

inline pmr::StatsResource::Stats GetResourceStats(const AabbTreeWorld& world) noexcept
inline std::optional<pmr::StatsResource::Stats> GetResourceStats(const AabbTreeWorld& world) noexcept
{
return world.m_statsResource.GetStats();
return world.m_statsResource.upstream_resource()
? world.m_statsResource.GetStats(): std::optional<pmr::StatsResource::Stats>{};
}

inline const ProxyIDs& GetProxies(const AabbTreeWorld& world) noexcept
Expand Down
29 changes: 29 additions & 0 deletions Library/include/playrho/d2/World.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <functional> // for std::function
#include <iterator>
#include <memory> // for std::unique_ptr
#include <optional>
#include <stdexcept>
#include <type_traits> // for std::is_default_constructible_v, etc.
#include <vector>
Expand All @@ -43,6 +44,8 @@
#include <playrho/StepStats.hpp>
#include <playrho/TypeInfo.hpp> // for GetTypeID

#include <playrho/pmr/StatsResource.hpp>

#include <playrho/d2/BodyConf.hpp> // for GetDefaultBodyConf
#include <playrho/d2/Body.hpp>
#include <playrho/d2/Joint.hpp>
Expand Down Expand Up @@ -135,6 +138,15 @@ std::add_pointer_t<std::add_const_t<T>> TypeCast(const World* value) noexcept;
template <typename T>
std::add_pointer_t<T> TypeCast(World* value) noexcept;

/// @brief Gets the polymorphic memory resource allocator statistics of the specified world.
/// @note This will be the empty value unless the world configuration the given world was
/// constructed with specified the collection of these statistics.
/// @note This information can be used to tweak the world configuration to pre-allocate enough
/// space to avoid the less deterministic performance behavior of dynamic memory allocation
/// during world step processing that may otherwise occur.
/// @see WorldConf.
std::optional<pmr::StatsResource::Stats> GetResourceStats(const World& world) noexcept;

/// @brief Clears the given world.
/// @note This calls the joint and shape destruction listeners (if they're set), for all
/// defined joints and shapes, before clearing anything. Any exceptions thrown from these
Expand Down Expand Up @@ -644,6 +656,7 @@ class World
friend std::add_pointer_t<std::add_const_t<T>> TypeCast(const World* value) noexcept;
template <typename T>
friend std::add_pointer_t<T> TypeCast(World* value) noexcept;
friend std::optional<pmr::StatsResource::Stats> GetResourceStats(const World& world) noexcept;
friend void Clear(World& world) noexcept;
friend StepStats Step(World& world, const StepConf& conf);
friend bool IsStepComplete(const World& world) noexcept;
Expand Down Expand Up @@ -746,6 +759,11 @@ class World
/// @brief Gets the data for the underlying configuration.
virtual void* GetData_() noexcept = 0;

/// @brief Gets the polymorphic memory resource statistics.
/// @note This will be the zero initialized value unless the world configuration the
/// world was constructed with specified the collection of these statistics.
virtual std::optional<pmr::StatsResource::Stats> GetResourceStats_() const noexcept = 0;

/// @brief Clears the world.
/// @note This calls the joint and shape destruction listeners (if they're set), for all
/// defined joints and shapes, before clearing anything. Any exceptions thrown from these
Expand Down Expand Up @@ -1147,6 +1165,12 @@ struct World::Model final: World::Concept {
return &data;
}

/// @copydoc Concept::GetResourceStats_
std::optional<pmr::StatsResource::Stats> GetResourceStats_() const noexcept override
{
return GetResourceStats(data);
}

/// @copydoc Concept::Clear_
void Clear_() noexcept override
{
Expand Down Expand Up @@ -1452,6 +1476,11 @@ inline TypeID GetType(const World& world) noexcept
return world.m_impl->GetType_();
}

inline std::optional<pmr::StatsResource::Stats> GetResourceStats(const World& world) noexcept
{
return world.m_impl->GetResourceStats_();
}

inline void Clear(World& world) noexcept
{
world.m_impl->Clear_();
Expand Down
5 changes: 5 additions & 0 deletions Library/include/playrho/d2/WorldConf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ struct WorldConf {
std::uint8_t reserveBuffers = DefaultReserveBuffers;

/// @brief Whether to collect resource statistics or not.
/// @note The collected statistics can help tweak the @c reserve* data members to help avoid
/// dynamic memory allocation during world step processing. Collecting these statistics
/// incurs some performance overhead however, so consider disabling this setting after
/// getting those data members tweaked to your needs.
/// @see GetResourceStats(const World&).
bool doStats = DefaultDoStats;
};

Expand Down
48 changes: 35 additions & 13 deletions Library/source/playrho/d2/AabbTreeWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,10 +680,10 @@ auto FindContacts(pmr::memory_resource& resource,
{
std::vector<AabbTreeWorld::ProxyKey, pmr::polymorphic_allocator<AabbTreeWorld::ProxyKey>> proxyKeys{&resource};
static constexpr auto DefaultReserveSize = 512u;
proxyKeys.reserve(DefaultReserveSize);
proxyKeys.reserve(DefaultReserveSize); // upper bound is current size of tree

// Accumalate contact keys for pairs of nodes that are overlapping and aren't identical.
// Note that if the dynamic tree node provides the body pointer, it's assumed to be faster
// Note that if the dynamic tree node provides the body index, it's assumed to be faster
// to eliminate any node pairs that have the same body here before the key pairs are
// sorted.
for_each(cbegin(proxies), cend(proxies), [&](DynamicTree::Size pid) {
Expand Down Expand Up @@ -804,7 +804,7 @@ auto SetAwake(ObjectPool<Body>& bodies, const Contact& c) -> void
} // anonymous namespace

AabbTreeWorld::AabbTreeWorld(const WorldConf& conf):
m_statsResource(conf.upstream),
m_statsResource(conf.doStats? conf.upstream: nullptr),
m_bodyStackResource(GetBodyStackOpts(conf),
conf.doStats? &m_statsResource: conf.upstream),
m_bodyConstraintsResource(GetBodyConstraintOpts(conf),
Expand All @@ -826,11 +826,22 @@ AabbTreeWorld::AabbTreeWorld(const WorldConf& conf):
}

AabbTreeWorld::AabbTreeWorld(const AabbTreeWorld& other):
m_bodyConstraintsResource(other.m_bodyConstraintsResource.GetOptions()),
m_positionConstraintsResource(other.m_positionConstraintsResource.GetOptions()),
m_velocityConstraintsResource(other.m_velocityConstraintsResource.GetOptions()),
m_proxyKeysResource(other.m_proxyKeysResource.GetOptions()),
m_islandResource(other.m_islandResource.GetOptions()),
m_statsResource(other.m_statsResource.upstream_resource()),
m_bodyConstraintsResource(other.m_bodyConstraintsResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_bodyConstraintsResource.GetUpstream()),
m_positionConstraintsResource(other.m_positionConstraintsResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_positionConstraintsResource.GetUpstream()),
m_velocityConstraintsResource(other.m_velocityConstraintsResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_velocityConstraintsResource.GetUpstream()),
m_proxyKeysResource(other.m_proxyKeysResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_proxyKeysResource.GetUpstream()),
m_islandResource(other.m_islandResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_islandResource.GetUpstream()),
m_tree(other.m_tree),
m_bodyBuffer(other.m_bodyBuffer),
m_shapeBuffer(other.m_shapeBuffer),
Expand All @@ -855,11 +866,22 @@ AabbTreeWorld::AabbTreeWorld(const AabbTreeWorld& other):
}

AabbTreeWorld::AabbTreeWorld(AabbTreeWorld&& other) noexcept:
m_bodyConstraintsResource(other.m_bodyConstraintsResource.GetOptions()),
m_positionConstraintsResource(other.m_positionConstraintsResource.GetOptions()),
m_velocityConstraintsResource(other.m_velocityConstraintsResource.GetOptions()),
m_proxyKeysResource(other.m_proxyKeysResource.GetOptions()),
m_islandResource(other.m_islandResource.GetOptions()),
m_statsResource(other.m_statsResource.upstream_resource()),
m_bodyConstraintsResource(other.m_bodyConstraintsResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_bodyConstraintsResource.GetUpstream()),
m_positionConstraintsResource(other.m_positionConstraintsResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_positionConstraintsResource.GetUpstream()),
m_velocityConstraintsResource(other.m_velocityConstraintsResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_velocityConstraintsResource.GetUpstream()),
m_proxyKeysResource(other.m_proxyKeysResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_proxyKeysResource.GetUpstream()),
m_islandResource(other.m_islandResource.GetOptions(),
other.m_statsResource.upstream_resource()?
&m_statsResource: other.m_islandResource.GetUpstream()),
m_tree(std::move(other.m_tree)),
m_bodyBuffer(std::move(other.m_bodyBuffer)),
m_shapeBuffer(std::move(other.m_shapeBuffer)),
Expand Down
3 changes: 3 additions & 0 deletions Library/source/playrho/pmr/StatsResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
*/

#include <algorithm> // for std::max
#include <cassert> // for assert

#include <playrho/pmr/StatsResource.hpp>

namespace playrho::pmr {

void *StatsResource::do_allocate(std::size_t bytes, std::size_t alignment)
{
assert(m_upstream != nullptr);
// Don't update statistics if allocate throws.
const auto p = m_upstream->allocate(bytes, alignment);
auto stats = m_stats;
Expand All @@ -41,6 +43,7 @@ void *StatsResource::do_allocate(std::size_t bytes, std::size_t alignment)

void StatsResource::do_deallocate(void *p, std::size_t bytes, std::size_t alignment)
{
assert(m_upstream != nullptr);
// Don't update statistics if deallocate throws.
m_upstream->deallocate(p, bytes, alignment);
auto stats = m_stats;
Expand Down
49 changes: 18 additions & 31 deletions UnitTests/AabbTreeWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ TEST(AabbTreeWorld, DefaultInit)
EXPECT_FALSE(IsLocked(world));

const auto stats = GetResourceStats(world);
EXPECT_EQ(stats.blocksAllocated, 0u);
EXPECT_EQ(stats.maxBlocksAllocated, 0u);
EXPECT_FALSE(stats.has_value());
}

TEST(AabbTreeWorld, Init)
Expand Down Expand Up @@ -231,24 +230,10 @@ TEST(AabbTreeWorld, GetResourceStatsWhenOff)
conf.reserveDistanceConstraints = 0u;
conf.reserveContactKeys = 0u;
auto world = AabbTreeWorld{conf};
auto stats = pmr::StatsResource::Stats{};
stats = GetResourceStats(world);
const auto oldstats = stats;
EXPECT_EQ(stats.blocksAllocated, 0u);
EXPECT_EQ(stats.bytesAllocated, 0u);
EXPECT_EQ(stats.maxBlocksAllocated, 0u);
EXPECT_EQ(stats.maxBytesAllocated, 0u);
EXPECT_EQ(stats.maxBytes, 0u);
EXPECT_EQ(stats.maxAlignment, 0u);
EXPECT_FALSE(GetResourceStats(world).has_value());
const auto stepConf = StepConf{};
Step(world, stepConf);
stats = GetResourceStats(world);
EXPECT_EQ(stats.blocksAllocated, oldstats.blocksAllocated);
EXPECT_EQ(stats.bytesAllocated, oldstats.bytesAllocated);
EXPECT_EQ(stats.maxBlocksAllocated, oldstats.maxBlocksAllocated);
EXPECT_EQ(stats.maxBytesAllocated, oldstats.maxBytesAllocated);
EXPECT_EQ(stats.maxBytes, oldstats.maxBytes);
EXPECT_EQ(stats.maxAlignment, oldstats.maxAlignment);
EXPECT_FALSE(GetResourceStats(world).has_value());
}

TEST(AabbTreeWorld, GetResourceStatsWhenOn)
Expand All @@ -261,24 +246,26 @@ TEST(AabbTreeWorld, GetResourceStatsWhenOn)
conf.reserveDistanceConstraints = 0u;
conf.reserveContactKeys = 0u;
auto world = AabbTreeWorld{conf};
auto stats = pmr::StatsResource::Stats{};
auto stats = std::optional<pmr::StatsResource::Stats>{};
stats = GetResourceStats(world);
ASSERT_TRUE(stats.has_value());
const auto oldstats = stats;
EXPECT_EQ(stats.blocksAllocated, 0u);
EXPECT_EQ(stats.bytesAllocated, 0u);
EXPECT_EQ(stats.maxBlocksAllocated, 0u);
EXPECT_EQ(stats.maxBytesAllocated, 0u);
EXPECT_EQ(stats.maxBytes, 0u);
EXPECT_EQ(stats.maxAlignment, 0u);
EXPECT_EQ(stats->blocksAllocated, 0u);
EXPECT_EQ(stats->bytesAllocated, 0u);
EXPECT_EQ(stats->maxBlocksAllocated, 0u);
EXPECT_EQ(stats->maxBytesAllocated, 0u);
EXPECT_EQ(stats->maxBytes, 0u);
EXPECT_EQ(stats->maxAlignment, 0u);
const auto stepConf = StepConf{};
Step(world, stepConf);
stats = GetResourceStats(world);
EXPECT_GT(stats.blocksAllocated, oldstats.blocksAllocated);
EXPECT_GT(stats.bytesAllocated, oldstats.bytesAllocated);
EXPECT_GT(stats.maxBlocksAllocated, oldstats.maxBlocksAllocated);
EXPECT_GT(stats.maxBytesAllocated, oldstats.maxBytesAllocated);
EXPECT_GT(stats.maxBytes, oldstats.maxBytes);
EXPECT_GT(stats.maxAlignment, oldstats.maxAlignment);
ASSERT_TRUE(stats.has_value());
EXPECT_GT(stats->blocksAllocated, oldstats->blocksAllocated);
EXPECT_GT(stats->bytesAllocated, oldstats->bytesAllocated);
EXPECT_GT(stats->maxBlocksAllocated, oldstats->maxBlocksAllocated);
EXPECT_GT(stats->maxBytesAllocated, oldstats->maxBytesAllocated);
EXPECT_GT(stats->maxBytes, oldstats->maxBytes);
EXPECT_GT(stats->maxAlignment, oldstats->maxAlignment);
}

TEST(AabbTreeWorld, CreateDestroyEmptyStaticBody)
Expand Down
45 changes: 45 additions & 0 deletions UnitTests/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3711,3 +3711,48 @@ static ::std::string gtest_WorldVerticalStackTest_EvalGenerateName_(const ::test

INSTANTIATE_TEST_CASE_P(World, VerticalStackTest, ::testing::Values(Real(0), Real(5)), test_suffix_generator);
#endif

TEST(World, GetResourceStatsWhenOff)
{
auto conf = WorldConf();
conf.doStats = false;
conf.reserveBuffers = 0;
conf.reserveBodyStack = 0u;
conf.reserveBodyConstraints = 0u;
conf.reserveDistanceConstraints = 0u;
conf.reserveContactKeys = 0u;
auto world = World{conf};
ASSERT_FALSE(GetResourceStats(world).has_value());
}

TEST(World, GetResourceStatsWhenOn)
{
auto conf = WorldConf();
conf.doStats = true;
conf.reserveBuffers = 0;
conf.reserveBodyStack = 0u;
conf.reserveBodyConstraints = 0u;
conf.reserveDistanceConstraints = 0u;
conf.reserveContactKeys = 0u;
auto world = World{conf};
auto stats = std::optional<pmr::StatsResource::Stats>{};
stats = GetResourceStats(world);
ASSERT_TRUE(stats.has_value());
const auto oldstats = stats;
EXPECT_EQ(stats->blocksAllocated, 0u);
EXPECT_EQ(stats->bytesAllocated, 0u);
EXPECT_EQ(stats->maxBlocksAllocated, 0u);
EXPECT_EQ(stats->maxBytesAllocated, 0u);
EXPECT_EQ(stats->maxBytes, 0u);
EXPECT_EQ(stats->maxAlignment, 0u);
const auto stepConf = StepConf{};
Step(world, stepConf);
stats = GetResourceStats(world);
ASSERT_TRUE(stats.has_value());
EXPECT_GT(stats->blocksAllocated, oldstats->blocksAllocated);
EXPECT_GT(stats->bytesAllocated, oldstats->bytesAllocated);
EXPECT_GT(stats->maxBlocksAllocated, oldstats->maxBlocksAllocated);
EXPECT_GT(stats->maxBytesAllocated, oldstats->maxBytesAllocated);
EXPECT_GT(stats->maxBytes, oldstats->maxBytes);
EXPECT_GT(stats->maxAlignment, oldstats->maxAlignment);
}

0 comments on commit 352d716

Please sign in to comment.