diff --git a/SDK/include/NDK/EntityList.cpp b/SDK/include/NDK/EntityList.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/SDK/include/NDK/EntityList.hpp b/SDK/include/NDK/EntityList.hpp index 4047388cc..22a632e65 100644 --- a/SDK/include/NDK/EntityList.hpp +++ b/SDK/include/NDK/EntityList.hpp @@ -16,45 +16,59 @@ namespace Ndk class NDK_API EntityList { public: - using Container = std::vector; + 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 m_entities; + inline std::size_t FindNext(std::size_t currentId) const; + inline World* GetWorld() const; + Nz::Bitset m_entityBits; + World* m_world; + }; + + class EntityList::iterator : public std::iterator + { + 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; }; } diff --git a/SDK/include/NDK/EntityList.inl b/SDK/include/NDK/EntityList.inl index 126af0a7e..6d9447a7f 100644 --- a/SDK/include/NDK/EntityList.inl +++ b/SDK/include/NDK/EntityList.inl @@ -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 #include #include +#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); } } diff --git a/SDK/include/NDK/Systems/RenderSystem.hpp b/SDK/include/NDK/Systems/RenderSystem.hpp index 61f7bd0a0..a74cb9d0e 100644 --- a/SDK/include/NDK/Systems/RenderSystem.hpp +++ b/SDK/include/NDK/Systems/RenderSystem.hpp @@ -57,7 +57,7 @@ namespace Ndk std::unique_ptr m_renderTechnique; std::vector m_volumeEntries; - EntityList m_cameras; + std::vector m_cameras; EntityList m_drawables; EntityList m_directionalLights; EntityList m_lights; diff --git a/SDK/include/NDK/World.hpp b/SDK/include/NDK/World.hpp index 9b2546eda..b1f1abde1 100644 --- a/SDK/include/NDK/World.hpp +++ b/SDK/include/NDK/World.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,7 @@ namespace Ndk friend Entity; public: - using EntityList = std::vector; + using EntityVector = std::vector; inline World(bool addDefaultSystems = true); World(const World&) = delete; @@ -41,13 +42,13 @@ namespace Ndk template 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 SystemType& GetSystem(); @@ -55,7 +56,7 @@ namespace Ndk template 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> m_systems; std::vector m_orderedSystems; std::vector m_entities; + std::vector m_entityBlocks; + std::vector> m_waitingEntities; std::vector m_freeIdList; EntityList m_aliveEntities; Nz::Bitset m_dirtyEntities; diff --git a/SDK/include/NDK/World.inl b/SDK/include/NDK/World.inl index dcdb0c057..45d358ad4 100644 --- a/SDK/include/NDK/World.inl +++ b/SDK/include/NDK/World.inl @@ -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) diff --git a/SDK/src/NDK/EntityList.cpp b/SDK/src/NDK/EntityList.cpp new file mode 100644 index 000000000..03fd3afc3 --- /dev/null +++ b/SDK/src/NDK/EntityList.cpp @@ -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 +#include + +namespace Ndk +{ + const EntityHandle& EntityList::iterator::operator*() const + { + return m_list->GetWorld()->GetEntity(static_cast(m_nextEntityId)); + } +} diff --git a/SDK/src/NDK/Systems/RenderSystem.cpp b/SDK/src/NDK/Systems/RenderSystem.cpp index f586056a2..44b0d1bba 100644 --- a/SDK/src/NDK/Systems/RenderSystem.cpp +++ b/SDK/src/NDK/Systems/RenderSystem.cpp @@ -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() && entity->HasComponent()) { - 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().GetLayer() < handle2->GetComponent().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() && entity->HasComponent()) { @@ -181,7 +198,7 @@ namespace Ndk GraphicsComponent& graphicsComponent = drawable->GetComponent(); graphicsComponent.EnsureBoundingVolumeUpdate(); } - + bool forceInvalidation = false; std::size_t visibilityHash = m_drawableCulling.Cull(camComponent.GetFrustum(), &forceInvalidation); diff --git a/SDK/src/NDK/World.cpp b/SDK/src/NDK/World.cpp index cad4660d4..db3cef529 100644 --- a/SDK/src/NDK/World.cpp +++ b/SDK/src/NDK/World.cpp @@ -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(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(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); }