Sdk/World: Add profiler

This commit is contained in:
Jérôme Leclercq 2018-02-08 16:25:27 +01:00
parent 6161b1a751
commit a9364ab7e2
5 changed files with 149 additions and 30 deletions

View File

@ -96,6 +96,7 @@ Nazara Development Kit:
- Fix CollisionComponent3D initialization (teleportation to their real coordinates) which could sometimes mess up the physics scene. - Fix CollisionComponent3D initialization (teleportation to their real coordinates) which could sometimes mess up the physics scene.
- ⚠️ Renamed World::Update() to World::Refresh() for more clarity and to differentiate it from World::Update(elapsedTime) - ⚠️ Renamed World::Update() to World::Refresh() for more clarity and to differentiate it from World::Update(elapsedTime)
- World entity ids are now reused from lowest to highest (they were previously reused in reverse order of death) - World entity ids are now reused from lowest to highest (they were previously reused in reverse order of death)
- World now has an internal profiler, allowing to measure the refresh and system update time
# 0.4: # 0.4:

View File

@ -29,6 +29,7 @@ namespace Ndk
public: public:
using EntityVector = std::vector<EntityHandle>; using EntityVector = std::vector<EntityHandle>;
struct ProfilerData;
inline World(bool addDefaultSystems = true); inline World(bool addDefaultSystems = true);
World(const World&) = delete; World(const World&) = delete;
@ -46,8 +47,12 @@ namespace Ndk
void Clear() noexcept; void Clear() noexcept;
const EntityHandle& CloneEntity(EntityId id); const EntityHandle& CloneEntity(EntityId id);
inline void DisableProfiler();
inline void EnableProfiler(bool enable = true);
inline const EntityHandle& GetEntity(EntityId id); inline const EntityHandle& GetEntity(EntityId id);
inline const EntityList& GetEntities() const; inline const EntityList& GetEntities() const;
inline const ProfilerData& GetProfilerData() const;
inline BaseSystem& GetSystem(SystemIndex index); inline BaseSystem& GetSystem(SystemIndex index);
template<typename SystemType> SystemType& GetSystem(); template<typename SystemType> SystemType& GetSystem();
@ -59,18 +64,27 @@ namespace Ndk
inline bool IsEntityValid(const Entity* entity) const; inline bool IsEntityValid(const Entity* entity) const;
inline bool IsEntityIdValid(EntityId id) const; inline bool IsEntityIdValid(EntityId id) const;
inline bool IsProfilerEnabled() const;
inline void RemoveAllSystems(); inline void RemoveAllSystems();
inline void RemoveSystem(SystemIndex index); inline void RemoveSystem(SystemIndex index);
template<typename SystemType> void RemoveSystem(); template<typename SystemType> void RemoveSystem();
void Refresh(); void Refresh();
inline void ResetProfiler();
void Update(float elapsedTime); void Update(float elapsedTime);
World& operator=(const World&) = delete; World& operator=(const World&) = delete;
inline World& operator=(World&& world) noexcept; inline World& operator=(World&& world) noexcept;
struct ProfilerData
{
Nz::UInt64 refreshTime = 0;
std::vector<Nz::UInt64> updateTime;
std::size_t updateCount = 0;
};
private: private:
inline void Invalidate(); inline void Invalidate();
inline void Invalidate(EntityId id); inline void Invalidate(EntityId id);
@ -97,10 +111,12 @@ namespace Ndk
std::vector<EntityBlock*> m_entityBlocks; std::vector<EntityBlock*> m_entityBlocks;
std::vector<std::unique_ptr<EntityBlock>> m_waitingEntities; std::vector<std::unique_ptr<EntityBlock>> m_waitingEntities;
EntityList m_aliveEntities; EntityList m_aliveEntities;
ProfilerData m_profilerData;
Nz::Bitset<Nz::UInt64> m_dirtyEntities; Nz::Bitset<Nz::UInt64> m_dirtyEntities;
Nz::Bitset<Nz::UInt64> m_freeEntityIds; Nz::Bitset<Nz::UInt64> m_freeEntityIds;
Nz::Bitset<Nz::UInt64> m_killedEntities; Nz::Bitset<Nz::UInt64> m_killedEntities;
bool m_orderedSystemsUpdated; bool m_orderedSystemsUpdated;
bool m_isProfilerEnabled;
}; };
} }

View File

@ -2,6 +2,7 @@
// This file is part of the "Nazara Development Kit" // This file is part of the "Nazara Development Kit"
// For conditions of distribution and use, see copyright notice in Prerequisites.hpp // For conditions of distribution and use, see copyright notice in Prerequisites.hpp
#include <NDK/World.hpp>
#include <Nazara/Core/Error.hpp> #include <Nazara/Core/Error.hpp>
#include <type_traits> #include <type_traits>
@ -14,7 +15,8 @@ namespace Ndk
*/ */
inline World::World(bool addDefaultSystems) : inline World::World(bool addDefaultSystems) :
m_orderedSystemsUpdated(false) m_orderedSystemsUpdated(false),
m_isProfilerEnabled(false)
{ {
if (addDefaultSystems) if (addDefaultSystems)
AddDefaultSystems(); AddDefaultSystems();
@ -47,7 +49,10 @@ namespace Ndk
// We must ensure that the vector is big enough to hold the new system // We must ensure that the vector is big enough to hold the new system
if (index >= m_systems.size()) if (index >= m_systems.size())
{
m_systems.resize(index + 1); m_systems.resize(index + 1);
m_profilerData.updateTime.resize(index + 1, 0);
}
// Affectation and return of system // Affectation and return of system
m_systems[index] = std::move(system); m_systems[index] = std::move(system);
@ -82,7 +87,6 @@ namespace Ndk
* *
* \param count Number of entities to create * \param count Number of entities to create
*/ */
inline World::EntityVector World::CreateEntities(unsigned int count) inline World::EntityVector World::CreateEntities(unsigned int count)
{ {
EntityVector list; EntityVector list;
@ -94,16 +98,79 @@ namespace Ndk
return list; return list;
} }
/*!
* \brief Disables the profiler, clearing up results
*
* This is just a shortcut to EnableProfiler(false)
*
* \param enable Should the entity be enabled
*
* \see EnableProfiler
*/
inline void World::DisableProfiler()
{
EnableProfiler(false);
}
/*!
* \brief Enables/Disables the internal profiler
*
* Worlds come with a built-in profiler, allowing to measure update count along with time passed in refresh and system updates.
* This is disabled by default as it adds an small overhead to the update process.
*
* \param enable Should the profiler be enabled
*
* \remark Disabling the profiler clears up results, as if ResetProfiler has been called
*/
inline void World::EnableProfiler(bool enable)
{
if (m_isProfilerEnabled != enable)
{
m_isProfilerEnabled = enable;
if (enable)
ResetProfiler();
}
}
/*!
* \brief Gets an entity
* \return A constant reference to a handle of the entity
*
* \param id Identifier of the entity
*
* \remark Handle referenced by this function can move in memory when updating the world, do not keep a handle reference from a world update to another
* \remark If an invalid identifier is provided, an error got triggered and an invalid handle is returned
*/
inline const EntityHandle& World::GetEntity(EntityId id)
{
if (IsEntityIdValid(id))
return m_entityBlocks[id]->handle;
else
{
NazaraError("Invalid ID");
return EntityHandle::InvalidHandle;
}
}
/*! /*!
* \brief Gets every entities in the world * \brief Gets every entities in the world
* \return A constant reference to the entities * \return A constant reference to the entities
*/ */
inline const EntityList& World::GetEntities() const inline const EntityList& World::GetEntities() const
{ {
return m_aliveEntities; return m_aliveEntities;
} }
/*!
* \brief Gets the latest profiler data
* \return A constant reference to the profiler data
*/
inline const World::ProfilerData& World::GetProfilerData() const
{
return m_profilerData;
}
/*! /*!
* \brief Gets a system in the world by index * \brief Gets a system in the world by index
* \return A reference to the system * \return A reference to the system
@ -192,26 +259,6 @@ namespace Ndk
KillEntity(entity); KillEntity(entity);
} }
/*!
* \brief Gets an entity
* \return A constant reference to a handle of the entity
*
* \param id Identifier of the entity
*
* \remark Handle referenced by this function can move in memory when updating the world, do not keep a handle reference from a world update to another
* \remark If an invalid identifier is provided, an error got triggered and an invalid handle is returned
*/
inline const EntityHandle& World::GetEntity(EntityId id)
{
if (IsEntityIdValid(id))
return m_entityBlocks[id]->handle;
else
{
NazaraError("Invalid ID");
return EntityHandle::InvalidHandle;
}
}
/*! /*!
* \brief Checks whether or not an entity is valid * \brief Checks whether or not an entity is valid
* \return true If it is the case * \return true If it is the case
@ -230,12 +277,22 @@ namespace Ndk
* *
* \param id Identifier of the entity * \param id Identifier of the entity
*/ */
inline bool World::IsEntityIdValid(EntityId id) const inline bool World::IsEntityIdValid(EntityId id) const
{ {
return id < m_entityBlocks.size() && m_entityBlocks[id]->entity.IsValid(); return id < m_entityBlocks.size() && m_entityBlocks[id]->entity.IsValid();
} }
/*!
* \brief Checks whether or not the profiler is enabled
* \return true If it is the case
*
* \see EnableProfiler
*/
inline bool World::IsProfilerEnabled() const
{
return m_isProfilerEnabled;
}
/*! /*!
* \brief Removes each system from the world * \brief Removes each system from the world
*/ */
@ -265,10 +322,21 @@ namespace Ndk
} }
} }
/*!
* \brief Clear profiler results
*
* This reset the profiler results, filling all counters with zero
*/
inline void World::ResetProfiler()
{
m_profilerData.refreshTime = 0;
m_profilerData.updateCount = 0;
std::fill(m_profilerData.updateTime.begin(), m_profilerData.updateTime.end(), 0);
}
/*! /*!
* \brief Removes a system from the world by type * \brief Removes a system from the world by type
*/ */
template<typename SystemType> template<typename SystemType>
void World::RemoveSystem() void World::RemoveSystem()
{ {
@ -292,6 +360,8 @@ namespace Ndk
m_killedEntities = std::move(world.m_killedEntities); m_killedEntities = std::move(world.m_killedEntities);
m_orderedSystems = std::move(world.m_orderedSystems); m_orderedSystems = std::move(world.m_orderedSystems);
m_orderedSystemsUpdated = world.m_orderedSystemsUpdated; m_orderedSystemsUpdated = world.m_orderedSystemsUpdated;
m_profilerData = std::move(world.m_profilerData);
m_isProfilerEnabled = m_isProfilerEnabled;
m_entities = std::move(world.m_entities); m_entities = std::move(world.m_entities);
for (EntityBlock& block : m_entities) for (EntityBlock& block : m_entities)

View File

@ -179,6 +179,10 @@ namespace Ndk
world.BindMethod("CreateEntity", &World::CreateEntity); world.BindMethod("CreateEntity", &World::CreateEntity);
world.BindMethod("CreateEntities", &World::CreateEntities); world.BindMethod("CreateEntities", &World::CreateEntities);
world.BindMethod("Clear", &World::Clear); world.BindMethod("Clear", &World::Clear);
world.BindMethod("DisableProfiler", &World::DisableProfiler);
world.BindMethod("EnableProfiler", &World::EnableProfiler);
world.BindMethod("IsProfilerEnabled", &World::IsProfilerEnabled);
world.BindMethod("ResetProfiler", &World::ResetProfiler);
world.BindMethod("IsValidHandle", &WorldHandle::IsValid); world.BindMethod("IsValidHandle", &WorldHandle::IsValid);
} }

View File

@ -3,6 +3,7 @@
// For conditions of distribution and use, see copyright notice in Prerequisites.hpp // For conditions of distribution and use, see copyright notice in Prerequisites.hpp
#include <NDK/World.hpp> #include <NDK/World.hpp>
#include <Nazara/Core/Clock.hpp>
#include <Nazara/Core/Error.hpp> #include <Nazara/Core/Error.hpp>
#include <NDK/BaseComponent.hpp> #include <NDK/BaseComponent.hpp>
#include <NDK/Systems/PhysicsSystem2D.hpp> #include <NDK/Systems/PhysicsSystem2D.hpp>
@ -177,6 +178,10 @@ namespace Ndk
* - Destroying dead entities and allowing their ids to be used by newly created entities * - Destroying dead entities and allowing their ids to be used by newly created entities
* - Update dirty entities, destroying their removed components and filtering them along systems * - Update dirty entities, destroying their removed components and filtering them along systems
* *
* \remark Calling this outside of Update will not increase the profiler values
*
* \see GetProfilerData
* \see Update
*/ */
void World::Refresh() void World::Refresh()
{ {
@ -257,17 +262,40 @@ namespace Ndk
/*! /*!
* \brief Updates the world * \brief Updates the world
*
* \param elapsedTime Delta time used for the update * \param elapsedTime Delta time used for the update
*
* This function Refreshes the world and calls the Update function of every active system part of it with the elapsedTime value.
* It also increase the profiler data with the elapsed time passed in Refresh and every system update.
*/ */
void World::Update(float elapsedTime) void World::Update(float elapsedTime)
{ {
Refresh(); //< Update entities if (m_isProfilerEnabled)
{
Nz::UInt64 t1 = Nz::GetElapsedMicroseconds();
Refresh();
Nz::UInt64 t2 = Nz::GetElapsedMicroseconds();
m_profilerData.refreshTime += t2 - t1;
for (auto& systemPtr : m_orderedSystems)
{
systemPtr->Update(elapsedTime);
Nz::UInt64 t3 = Nz::GetElapsedMicroseconds();
m_profilerData.updateTime[systemPtr->GetIndex()] += t3 - t2;
t2 = t3;
}
m_profilerData.updateCount++;
}
else
{
Refresh();
// And then update systems
for (auto& systemPtr : m_orderedSystems) for (auto& systemPtr : m_orderedSystems)
systemPtr->Update(elapsedTime); systemPtr->Update(elapsedTime);
} }
}
void World::ReorderSystems() void World::ReorderSystems()
{ {