Refactor EntityList and prevent World to invalidate its own handles between updates

This commit is contained in:
Lynix 2017-04-20 23:42:45 +02:00
parent 0a75bce99d
commit 48b348135e
9 changed files with 231 additions and 144 deletions

View File

View File

@ -16,45 +16,59 @@ namespace Ndk
class NDK_API EntityList
{
public:
using Container = std::vector<EntityHandle>;
class iterator;
friend iterator;
using size_type = std::size_t;
EntityList() = default;
inline EntityList();
~EntityList() = default;
inline void Clear();
inline bool Has(const Entity* entity);
inline bool Has(EntityId entity);
inline bool Has(const Entity* entity) const;
inline bool Has(EntityId entity) const;
inline void Insert(Entity* entity);
inline void Remove(Entity* entity);
// STL API
inline Container::iterator begin();
inline Container::const_iterator begin() const;
inline Container::const_iterator cbegin() const;
inline Container::const_iterator cend() const;
inline Container::const_reverse_iterator crbegin() const;
inline Container::const_reverse_iterator crend() const;
inline iterator begin();
inline bool empty() const;
inline Container::iterator end();
inline Container::const_iterator end() const;
inline Container::reverse_iterator rbegin();
inline Container::const_reverse_iterator rbegin() const;
inline Container::reverse_iterator rend();
inline Container::const_reverse_iterator rend() const;
inline Container::size_type size() const;
inline iterator end();
inline size_type size() const;
private:
std::vector<EntityHandle> m_entities;
inline std::size_t FindNext(std::size_t currentId) const;
inline World* GetWorld() const;
Nz::Bitset<Nz::UInt64> m_entityBits;
World* m_world;
};
class EntityList::iterator : public std::iterator<std::forward_iterator_tag, const EntityHandle>
{
friend EntityList;
public:
inline iterator(const iterator& iterator);
const EntityHandle& operator*() const;
inline iterator& operator=(const iterator& iterator);
inline iterator& operator++();
inline iterator operator++(int);
friend inline bool operator==(const iterator& lhs, const iterator& rhs);
friend inline bool operator!=(const iterator& lhs, const iterator& rhs);
friend inline void swap(iterator& lhs, iterator& rhs);
private:
inline iterator(const EntityList* world, std::size_t nextId);
std::size_t m_nextEntityId;
const EntityList* m_list;
};
}

View File

@ -2,8 +2,10 @@
// This file is part of the "Nazara Development Kit"
// For conditions of distribution and use, see copyright notice in Prerequesites.hpp
#include <NDK/EntityList.hpp>
#include <Nazara/Core/Error.hpp>
#include <algorithm>
#include "EntityList.hpp"
namespace Ndk
{
@ -16,22 +18,34 @@ namespace Ndk
/*!
* \brief Clears the set from every entities
*/
inline void EntityList::Clear()
inline EntityList::EntityList() :
m_world(nullptr)
{
m_entities.clear();
m_entityBits.Clear();
}
/*!
* \brief Checks whether or not the set contains the entity
* \brief Clears the set from every entities
*
* \remark This resets the implicit world member, allowing you to insert entities from a different world than previously
*/
inline void EntityList::Clear()
{
m_entityBits.Clear();
m_world = nullptr;
}
/*!
* \brief Checks whether or not the EntityList contains the entity
* \return true If it is the case
*
* \param entity Pointer to the entity
*
* \remark If the Insert function was called since the EntityList construction (or last call to Clear), the entity passed by parameter must belong to the same world as the previously inserted entities.
*/
inline bool EntityList::Has(const Entity* entity)
inline bool EntityList::Has(const Entity* entity) const
{
NazaraAssert(!m_world || !entity || entity->GetWorld() == m_world, "Incompatible world");
return entity && entity->IsValid() && Has(entity->GetId());
}
@ -41,8 +55,7 @@ namespace Ndk
*
* \param id Identifier of the entity
*/
inline bool EntityList::Has(EntityId entity)
inline bool EntityList::Has(EntityId entity) const
{
return m_entityBits.UnboundedTest(entity);
}
@ -50,18 +63,18 @@ namespace Ndk
/*!
* \brief Inserts the entity into the set
*
* \param entity Pointer to the entity
* \param entity Valid pointer to an entity
*
* \remark If entity is already contained, no action is performed
* \remark If any entity has been inserted since construction (or last Clear call), the entity must belong to the same world as the previously inserted entities
*/
inline void EntityList::Insert(Entity* entity)
{
if (!Has(entity))
{
m_entities.emplace_back(entity);
m_entityBits.UnboundedSet(entity->GetId(), true);
}
NazaraAssert(entity, "Invalid entity");
NazaraAssert(!m_world || entity->GetWorld() == m_world, "Incompatible world");
m_entityBits.UnboundedSet(entity->GetId(), true);
m_world = entity->GetWorld();
}
/*!
@ -70,89 +83,101 @@ namespace Ndk
* \param entity Pointer to the entity
*
* \remark If entity is not contained, no action is performed
* \remark This function never resets the implicit world member, even if it empties the list. Use the Clear method if you want to reset it.
*
* \see Clear
*/
inline void EntityList::Remove(Entity* entity)
{
if (Has(entity))
{
auto it = std::find(m_entities.begin(), m_entities.end(), *entity);
NazaraAssert(it != m_entities.end(), "Entity should be part of the vector");
std::swap(*it, m_entities.back());
m_entities.pop_back(); // We get it out of the vector
m_entityBits.UnboundedSet(entity->GetId(), false);
}
m_entityBits.UnboundedSet(entity->GetId(), false);
}
// Nz::Interface STD
inline EntityList::Container::iterator EntityList::begin()
// STL Interface
inline EntityList::iterator EntityList::begin()
{
return m_entities.begin();
}
inline EntityList::Container::const_iterator EntityList::begin() const
{
return m_entities.begin();
}
inline EntityList::Container::const_iterator EntityList::cbegin() const
{
return m_entities.cbegin();
}
inline EntityList::Container::const_iterator EntityList::cend() const
{
return m_entities.cend();
}
inline EntityList::Container::const_reverse_iterator EntityList::crbegin() const
{
return m_entities.crbegin();
}
inline EntityList::Container::const_reverse_iterator EntityList::crend() const
{
return m_entities.crend();
return EntityList::iterator(this, m_entityBits.FindFirst());
}
inline bool EntityList::empty() const
{
return m_entities.empty();
return m_entityBits.TestAny();
}
inline EntityList::Container::iterator EntityList::end()
inline EntityList::iterator EntityList::end()
{
return m_entities.end();
return EntityList::iterator(this, m_entityBits.npos);
}
inline EntityList::Container::const_iterator EntityList::end() const
inline EntityList::size_type EntityList::size() const
{
return m_entities.end();
return m_entityBits.Count();
}
inline EntityList::Container::reverse_iterator EntityList::rbegin()
inline std::size_t EntityList::FindNext(std::size_t currentId) const
{
return m_entities.rbegin();
return m_entityBits.FindNext(currentId);
}
inline EntityList::Container::const_reverse_iterator EntityList::rbegin() const
inline World* EntityList::GetWorld() const
{
return m_entities.rbegin();
return m_world;
}
inline EntityList::Container::reverse_iterator EntityList::rend()
inline EntityList::iterator::iterator(const EntityList* list, std::size_t nextId) :
m_nextEntityId(nextId),
m_list(list)
{
return m_entities.rend();
}
inline EntityList::Container::const_reverse_iterator EntityList::rend() const
inline EntityList::iterator::iterator(const iterator& iterator) :
m_nextEntityId(iterator.m_nextEntityId),
m_list(iterator.m_list)
{
return m_entities.rend();
}
inline EntityList::Container::size_type EntityList::size() const
inline EntityList::iterator& EntityList::iterator::operator=(const iterator& iterator)
{
return m_entities.size();
m_nextEntityId = iterator.m_nextEntityId;
m_list = iterator.m_list;
return *this;
}
inline EntityList::iterator& EntityList::iterator::operator++()
{
m_nextEntityId = m_list->FindNext(m_nextEntityId);
return *this;
}
inline EntityList::iterator EntityList::iterator::operator++(int)
{
std::size_t previousId = m_nextEntityId;
m_nextEntityId = m_list->FindNext(m_nextEntityId);
return iterator(m_list, previousId);
}
inline bool operator==(const EntityList::iterator& lhs, const EntityList::iterator& rhs)
{
NazaraAssert(lhs.m_list == rhs.m_list, "Cannot compare iterator coming from different lists");
return lhs.m_nextEntityId == rhs.m_nextEntityId;
}
inline bool operator!=(const EntityList::iterator& lhs, const EntityList::iterator& rhs)
{
return !operator==(lhs, rhs);
}
inline void swap(EntityList::iterator& lhs, EntityList::iterator& rhs)
{
NazaraAssert(lhs.m_list == rhs.m_list, "Cannot compare iterator coming from different lists");
using std::swap;
std::swap(lhs.m_nextEntityId, rhs.m_nextEntityId);
}
}

View File

@ -57,7 +57,7 @@ namespace Ndk
std::unique_ptr<Nz::AbstractRenderTechnique> m_renderTechnique;
std::vector<GraphicsComponentCullingList::VolumeEntry> m_volumeEntries;
EntityList m_cameras;
std::vector<EntityHandle> m_cameras;
EntityList m_drawables;
EntityList m_directionalLights;
EntityList m_lights;

View File

@ -10,6 +10,7 @@
#include <Nazara/Core/Bitset.hpp>
#include <Nazara/Core/HandledObject.hpp>
#include <NDK/Entity.hpp>
#include <NDK/EntityList.hpp>
#include <NDK/System.hpp>
#include <algorithm>
#include <memory>
@ -28,7 +29,7 @@ namespace Ndk
friend Entity;
public:
using EntityList = std::vector<EntityHandle>;
using EntityVector = std::vector<EntityHandle>;
inline World(bool addDefaultSystems = true);
World(const World&) = delete;
@ -41,13 +42,13 @@ namespace Ndk
template<typename SystemType, typename... Args> SystemType& AddSystem(Args&&... args);
const EntityHandle& CreateEntity();
inline EntityList CreateEntities(unsigned int count);
inline EntityVector CreateEntities(unsigned int count);
void Clear() noexcept;
const EntityHandle& CloneEntity(EntityId id);
const EntityHandle& GetEntity(EntityId id);
inline const EntityList& GetEntities();
inline const EntityList& GetEntities() const;
inline BaseSystem& GetSystem(SystemIndex index);
template<typename SystemType> SystemType& GetSystem();
@ -55,7 +56,7 @@ namespace Ndk
template<typename SystemType> bool HasSystem() const;
void KillEntity(Entity* entity);
inline void KillEntities(const EntityList& list);
inline void KillEntities(const EntityVector& list);
inline bool IsEntityValid(const Entity* entity) const;
inline bool IsEntityIdValid(EntityId id) const;
@ -79,19 +80,22 @@ namespace Ndk
struct EntityBlock
{
EntityBlock(Entity&& e) :
entity(std::move(e))
entity(std::move(e)),
handle(&entity)
{
}
EntityBlock(EntityBlock&& block) = default;
Entity entity;
std::size_t aliveIndex;
EntityHandle handle;
};
std::vector<std::unique_ptr<BaseSystem>> m_systems;
std::vector<BaseSystem*> m_orderedSystems;
std::vector<EntityBlock> m_entities;
std::vector<EntityBlock*> m_entityBlocks;
std::vector<std::unique_ptr<EntityBlock>> m_waitingEntities;
std::vector<EntityId> m_freeIdList;
EntityList m_aliveEntities;
Nz::Bitset<Nz::UInt64> m_dirtyEntities;

View File

@ -82,9 +82,9 @@ namespace Ndk
* \param count Number of entities to create
*/
inline World::EntityList World::CreateEntities(unsigned int count)
inline World::EntityVector World::CreateEntities(unsigned int count)
{
EntityList list;
EntityVector list;
list.reserve(count);
for (unsigned int i = 0; i < count; ++i)
@ -98,7 +98,7 @@ namespace Ndk
* \return A constant reference to the entities
*/
inline const World::EntityList& World::GetEntities()
inline const EntityList& World::GetEntities() const
{
return m_aliveEntities;
}
@ -170,7 +170,7 @@ namespace Ndk
* \param list Set of entities to kill
*/
inline void World::KillEntities(const EntityList& list)
inline void World::KillEntities(const EntityVector& list)
{
for (const EntityHandle& entity : list)
KillEntity(entity);
@ -197,7 +197,7 @@ namespace Ndk
inline bool World::IsEntityIdValid(EntityId id) const
{
return id < m_entities.size() && m_entities[id].entity.IsValid();
return id < m_entityBlocks.size() && m_entityBlocks[id]->entity.IsValid();
}
/*!
@ -270,6 +270,7 @@ namespace Ndk
m_killedEntities = std::move(world.m_killedEntities);
m_orderedSystems = std::move(world.m_orderedSystems);
m_orderedSystemsUpdated = world.m_orderedSystemsUpdated;
m_waitingEntities = std::move(world.m_waitingEntities);
m_entities = std::move(world.m_entities);
for (EntityBlock& block : m_entities)

View File

@ -0,0 +1,14 @@
// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Development Kit"
// For conditions of distribution and use, see copyright notice in Prerequesites.hpp
#include <NDK/EntityList.hpp>
#include <NDK/World.hpp>
namespace Ndk
{
const EntityHandle& EntityList::iterator::operator*() const
{
return m_list->GetWorld()->GetEntity(static_cast<EntityId>(m_nextEntityId));
}
}

View File

@ -48,7 +48,15 @@ namespace Ndk
{
m_forceRenderQueueInvalidation = true; //< Hackfix until lights and particles are handled by culling list
m_cameras.Remove(entity);
for (auto it = m_cameras.begin(); it != m_cameras.end(); ++it)
{
if (it->GetObject() == entity)
{
m_cameras.erase(it);
break;
}
}
m_directionalLights.Remove(entity);
m_drawables.Remove(entity);
m_lights.Remove(entity);
@ -74,14 +82,23 @@ namespace Ndk
if (entity->HasComponent<CameraComponent>() && entity->HasComponent<NodeComponent>())
{
m_cameras.Insert(entity);
m_cameras.emplace_back(entity);
std::sort(m_cameras.begin(), m_cameras.end(), [](const EntityHandle& handle1, const EntityHandle& handle2)
{
return handle1->GetComponent<CameraComponent>().GetLayer() < handle2->GetComponent<CameraComponent>().GetLayer();
});
}
else
m_cameras.Remove(entity);
{
for (auto it = m_cameras.begin(); it != m_cameras.end(); ++it)
{
if (it->GetObject() == entity)
{
m_cameras.erase(it);
break;
}
}
}
if (entity->HasComponent<GraphicsComponent>() && entity->HasComponent<NodeComponent>())
{
@ -181,7 +198,7 @@ namespace Ndk
GraphicsComponent& graphicsComponent = drawable->GetComponent<GraphicsComponent>();
graphicsComponent.EnsureBoundingVolumeUpdate();
}
bool forceInvalidation = false;
std::size_t visibilityHash = m_drawableCulling.Cull(camComponent.GetFrustum(), &forceInvalidation);

View File

@ -60,29 +60,47 @@ namespace Ndk
const EntityHandle& World::CreateEntity()
{
EntityId id;
EntityBlock* entBlock;
if (!m_freeIdList.empty())
{
// We get an identifier
id = m_freeIdList.back();
m_freeIdList.pop_back();
entBlock = &m_entities[id];
m_entityBlocks[id] = entBlock;
}
else
{
// We allocate a new entity
id = static_cast<Ndk::EntityId>(m_entities.size());
// We can't use emplace_back due to the scope
m_entities.push_back(Entity(this, id));
if (m_entities.capacity() > m_entities.size())
{
m_entities.push_back(Entity(this, id)); //< We can't use emplace_back due to the scope
entBlock = &m_entities.back();
}
else
{
// Pushing to entities would reallocate vector and thus, invalidate EntityHandles (which we don't want until world update)
// To prevent this, allocate them into a separate vector and move them at update
// For now, we are counting on m_entities grow strategy to prevent
m_waitingEntities.emplace_back(std::make_unique<EntityBlock>(Entity(this, id)));
entBlock = m_waitingEntities.back().get();
}
if (id >= m_entityBlocks.size())
m_entityBlocks.resize(id + 1);
m_entityBlocks[id] = entBlock;
}
// We initialise the entity and we add it to the list of alive entities
Entity& entity = m_entities[id].entity;
entity.Create();
// We initialize the entity and we add it to the list of alive entities
entBlock->entity.Create();
m_aliveEntities.emplace_back(&entity);
m_entities[id].aliveIndex = m_aliveEntities.size() - 1;
m_aliveEntities.Insert(&entBlock->entity);
return m_aliveEntities.back();
return entBlock->handle;
}
/*!
@ -97,7 +115,7 @@ namespace Ndk
// This is made to avoid that handle warn uselessly entities before their destruction
m_entities.clear();
m_aliveEntities.clear();
m_aliveEntities.Clear();
m_dirtyEntities.Clear();
m_killedEntities.Clear();
}
@ -158,7 +176,7 @@ namespace Ndk
const EntityHandle& World::GetEntity(EntityId id)
{
if (IsEntityIdValid(id))
return m_aliveEntities[m_entities[id].aliveIndex];
return m_entities[id].handle;
else
{
NazaraError("Invalid ID");
@ -177,45 +195,39 @@ namespace Ndk
if (!m_orderedSystemsUpdated)
ReorderSystems();
// Move waiting entities to entity list
if (!m_waitingEntities.empty())
{
constexpr std::size_t MinEntityCapacity = 10; //< We want to be able to grow entity count by at least ten entities per update without going to the waiting list
m_entities.reserve(m_entities.size() + m_waitingEntities.size() + MinEntityCapacity);
for (auto& blockPtr : m_waitingEntities)
m_entities.push_back(std::move(*blockPtr));
m_waitingEntities.clear();
}
// Handle killed entities before last call
for (std::size_t i = m_killedEntities.FindFirst(); i != m_killedEntities.npos; i = m_killedEntities.FindNext(i))
{
EntityBlock& block = m_entities[i];
Entity& entity = block.entity;
NazaraAssert(i < m_entityBlocks.size(), "Entity index out of range");
NazaraAssert(entity.IsValid(), "Entity must be valid");
Entity* entity = &m_entityBlocks[i]->entity;
// Destruction of the entity (invalidation of handle by the same way)
entity.Destroy();
entity->Destroy();
// Send back the identifier of the entity to the free queue
m_freeIdList.push_back(entity.GetId());
// We take out the handle from the list of alive entities
// With the idiom swap and pop
NazaraAssert(block.aliveIndex < m_aliveEntities.size(), "Alive index out of range");
if (block.aliveIndex < m_aliveEntities.size() - 1) // If it's not the last handle
{
EntityHandle& lastHandle = m_aliveEntities.back();
EntityHandle& myHandle = m_aliveEntities[block.aliveIndex];
myHandle = std::move(lastHandle);
// We don't forget to update the index associated to the entity
m_entities[myHandle->GetId()].aliveIndex = block.aliveIndex;
}
m_aliveEntities.pop_back();
m_freeIdList.push_back(entity->GetId());
}
m_killedEntities.Reset();
// Handle of entities which need an update from the systems
for (std::size_t i = m_dirtyEntities.FindFirst(); i != m_dirtyEntities.npos; i = m_dirtyEntities.FindNext(i))
{
NazaraAssert(i < m_entities.size(), "Entity index out of range");
NazaraAssert(i < m_entityBlocks.size(), "Entity index out of range");
Entity* entity = &m_entities[i].entity;
Entity* entity = &m_entityBlocks[i]->entity;
// Check entity validity (as it could have been reported as dirty and killed during the same iteration)
if (!entity->IsValid())
@ -242,7 +254,7 @@ namespace Ndk
}
else
{
// No, it shouldn't, remove it if it's part of the system
// No it shouldn't, remove it if it's part of the system
if (partOfSystem)
system->RemoveEntity(entity);
}