diff --git a/SDK/include/NDK/Algorithm.hpp b/SDK/include/NDK/Algorithm.hpp index 68d4711d4..0e6758564 100644 --- a/SDK/include/NDK/Algorithm.hpp +++ b/SDK/include/NDK/Algorithm.hpp @@ -12,7 +12,9 @@ namespace Ndk { template ComponentId BuildComponentId(const char (&id)[N]); + template SystemId BuildSystemId(const char (&id)[N]); template constexpr ComponentId GetComponentId(); + template constexpr SystemId GetSystemId(); } #include diff --git a/SDK/include/NDK/Algorithm.inl b/SDK/include/NDK/Algorithm.inl index dbe2b292d..8d4710d34 100644 --- a/SDK/include/NDK/Algorithm.inl +++ b/SDK/include/NDK/Algorithm.inl @@ -19,9 +19,27 @@ namespace Ndk return componentId; } + template + SystemId BuildSystemId(const char (&id)[N]) + { + static_assert(N-1 <= sizeof(ComponentId), "ID too long for this size of component id"); + + ComponentId componentId = 0; + for (int i = 0; i < N; ++i) + componentId |= static_cast(id[i]) << i*8; + + return componentId; + } + template constexpr ComponentId GetComponentId() { return ComponentType::ComponentId; } + + template + constexpr SystemId GetSystemId() + { + return SystemType::SystemId; + } } diff --git a/SDK/include/NDK/BaseSystem.hpp b/SDK/include/NDK/BaseSystem.hpp new file mode 100644 index 000000000..0a8e1a9d5 --- /dev/null +++ b/SDK/include/NDK/BaseSystem.hpp @@ -0,0 +1,68 @@ +// Copyright (C) 2015 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 + +#pragma once + +#ifndef NDK_BASESYSTEM_HPP +#define NDK_BASESYSTEM_HPP + +#include +#include +#include +#include + +namespace Ndk +{ + class World; + + class NDK_API BaseSystem + { + friend class Entity; + friend World; + + public: + BaseSystem(SystemId systemId); + virtual ~BaseSystem(); + + virtual BaseSystem* Clone() const = 0; + + bool Filters(const EntityHandle& entity) const; + + const std::vector& GetEntities() const; + SystemId GetId() const; + World& GetWorld() const; + + bool HasEntity(const EntityHandle& entity) const; + + protected: + template void Excludes(); + template void Excludes(); + void ExcludesComponent(ComponentId componentId); + + template void Requires(); + template void Requires(); + void RequiresComponent(ComponentId componentId); + + private: + void AddEntity(const EntityHandle& entity); + + virtual void OnEntityAdded(const EntityHandle& entity); + virtual void OnEntityRemoved(const EntityHandle& entity); + + void RemoveEntity(const EntityHandle& entity); + + void SetWorld(World& world); + + std::vector m_entities; + NzBitset m_entityBits; + std::unordered_set m_excludedComponents; + std::unordered_set m_requiredComponents; + SystemId m_systemId; + World* m_world; + }; +} + +#include + +#endif // NDK_BASESYSTEM_HPP diff --git a/SDK/include/NDK/BaseSystem.inl b/SDK/include/NDK/BaseSystem.inl new file mode 100644 index 000000000..f4edda7ae --- /dev/null +++ b/SDK/include/NDK/BaseSystem.inl @@ -0,0 +1,104 @@ +// Copyright (C) 2015 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 +{ + inline BaseSystem::BaseSystem(SystemId systemId) : + m_systemId(systemId) + { + } + + inline const std::vector& BaseSystem::GetEntities() const + { + return m_entities; + } + + inline SystemId BaseSystem::GetId() const + { + return m_systemId; + } + + inline World& BaseSystem::GetWorld() const + { + return *m_world; + } + + inline bool BaseSystem::HasEntity(const EntityHandle& entity) const + { + return m_entityBits.UnboundedTest(entity->GetId()); + } + + template + void BaseSystem::Excludes() + { + static_assert(std::is_base_of(), "ComponentType is not a component"); + + ExcludesComponent(GetComponentId()); + } + + template + void BaseSystem::Excludes() + { + Excludes(); + Excludes(); + } + + inline void BaseSystem::ExcludesComponent(ComponentId componentId) + { + m_excludedComponents.insert(componentId); + } + + template + void BaseSystem::Requires() + { + static_assert(std::is_base_of(), "ComponentType is not a component"); + + RequiresComponent(GetComponentId()); + } + + template + void BaseSystem::Requires() + { + Requires(); + Requires(); + } + + inline void BaseSystem::RequiresComponent(ComponentId componentId) + { + m_requiredComponents.insert(componentId); + } + + inline void BaseSystem::AddEntity(const EntityHandle& entity) + { + m_entities.push_back(entity); + m_entityBits.UnboundedSet(entity->GetId(), true); + + entity->RegisterSystem(m_systemId); + + OnEntityAdded(entity); + } + + inline void BaseSystem::RemoveEntity(const EntityHandle& entity) + { + auto it = std::find(m_entities.begin(), m_entities.end(), entity); + NazaraAssert(it != m_entities.end(), "Entity is not part of this system"); + + // Pour éviter de déplacer beaucoup de handles, on swap le dernier avec celui à supprimer + std::swap(*it, m_entities.back()); + m_entities.pop_back(); // On le sort du vector + + m_entityBits.Reset(entity->GetId()); + entity->UnregisterSystem(m_systemId); + + OnEntityRemoved(entity); // Et on appelle le callback + } + + inline void BaseSystem::SetWorld(World& world) + { + m_world = &world; + } +} diff --git a/SDK/include/NDK/Entity.hpp b/SDK/include/NDK/Entity.hpp index d15c46625..413ee358e 100644 --- a/SDK/include/NDK/Entity.hpp +++ b/SDK/include/NDK/Entity.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace Ndk { @@ -20,6 +21,7 @@ namespace Ndk class NDK_API Entity { + friend class BaseSystem; friend EntityHandle; friend World; @@ -59,10 +61,13 @@ namespace Ndk void Destroy(); void RegisterHandle(EntityHandle* handle); + void RegisterSystem(SystemId systemId); void UnregisterHandle(EntityHandle* handle); + void UnregisterSystem(SystemId systemId); std::vector m_handles; std::unordered_map> m_components; + std::unordered_set m_systems; EntityId m_id; World* m_world; bool m_valid; diff --git a/SDK/include/NDK/Entity.inl b/SDK/include/NDK/Entity.inl index 0f6489b24..33dbbce2c 100644 --- a/SDK/include/NDK/Entity.inl +++ b/SDK/include/NDK/Entity.inl @@ -85,6 +85,11 @@ namespace Ndk m_handles.push_back(handle); } + inline void Entity::RegisterSystem(SystemId systemId) + { + m_systems.insert(systemId); + } + inline void Entity::UnregisterHandle(EntityHandle* handle) { ///DOC: Un handle ne doit être libéré qu'une fois, et doit faire partie de la liste, sous peine de crash @@ -94,4 +99,9 @@ namespace Ndk std::swap(*it, m_handles.back()); m_handles.pop_back(); } + + inline void Entity::UnregisterSystem(SystemId systemId) + { + m_systems.erase(systemId); + } } diff --git a/SDK/include/NDK/Prerequesites.hpp b/SDK/include/NDK/Prerequesites.hpp index f83ef5506..894671b2b 100644 --- a/SDK/include/NDK/Prerequesites.hpp +++ b/SDK/include/NDK/Prerequesites.hpp @@ -59,6 +59,7 @@ namespace Ndk { using ComponentId = nzUInt32; using EntityId = nzUInt32; + using SystemId = nzUInt32; } #endif // NDK_PREREQUESITES_HPP diff --git a/SDK/include/NDK/System.hpp b/SDK/include/NDK/System.hpp new file mode 100644 index 000000000..c49db14c5 --- /dev/null +++ b/SDK/include/NDK/System.hpp @@ -0,0 +1,27 @@ +// Copyright (C) 2015 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 + +#pragma once + +#ifndef NDK_SYSTEM_HPP +#define NDK_SYSTEM_HPP + +#include + +namespace Ndk +{ + template + class System : public BaseSystem + { + public: + System(); + virtual ~System(); + + BaseSystem* Clone() const override; + }; +} + +#include + +#endif // NDK_SYSTEM_HPP diff --git a/SDK/include/NDK/System.inl b/SDK/include/NDK/System.inl new file mode 100644 index 000000000..fa81072b1 --- /dev/null +++ b/SDK/include/NDK/System.inl @@ -0,0 +1,27 @@ +// Copyright (C) 2015 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 +{ + template + System::System() : + BaseSystem(GetSystemId()) + { + } + + template + System::~System() = default; + + template + BaseSystem* System::Clone() const + { + ///FIXME: Pas encore supporté par GCC (4.9.2) + //static_assert(std::is_trivially_copy_constructible::value, "SystemType should be copy-constructible"); + + return new SystemType(static_cast(*this)); + } +} diff --git a/SDK/include/NDK/World.hpp b/SDK/include/NDK/World.hpp index 6b21469dc..885053c54 100644 --- a/SDK/include/NDK/World.hpp +++ b/SDK/include/NDK/World.hpp @@ -11,25 +11,38 @@ #include #include #include +#include #include +#include #include +#include namespace Ndk { class NDK_API World : NzNonCopyable { + friend Entity; + public: using EntityList = std::vector; World() = default; ~World(); + BaseSystem& AddSystem(std::unique_ptr&& system); + template SystemType& AddSystem(Args&&... args); + EntityHandle CreateEntity(); EntityList CreateEntities(unsigned int count); void Clear(); const EntityHandle& GetEntity(EntityId id); + BaseSystem& GetSystem(SystemId systemId); + template SystemType& GetSystem(); + + bool HasSystem(SystemId systemId) const; + template bool HasSystem() const; void KillEntity(const EntityHandle& entity); void KillEntities(const EntityList& list); @@ -37,9 +50,16 @@ namespace Ndk bool IsEntityValid(const EntityHandle& entity) const; bool IsEntityIdValid(EntityId id) const; + void RemoveAllSystems(); + void RemoveSystem(SystemId systemId); + template void RemoveSystem(); + void Update(); private: + void MarkAllAsDirty(); + void MarkAsDirty(EntityId id); + struct EntityBlock { EntityBlock(Entity&& e) : @@ -53,7 +73,9 @@ namespace Ndk std::vector m_freeIdList; std::vector m_entities; + std::unordered_map> m_systems; EntityList m_aliveEntities; + NzBitset m_dirtyEntities; NzBitset m_killedEntities; }; } diff --git a/SDK/include/NDK/World.inl b/SDK/include/NDK/World.inl index 7ecc6d0ae..3b02e043e 100644 --- a/SDK/include/NDK/World.inl +++ b/SDK/include/NDK/World.inl @@ -3,9 +3,35 @@ // For conditions of distribution and use, see copyright notice in Prerequesites.hpp #include +#include namespace Ndk { + inline BaseSystem& World::AddSystem(std::unique_ptr&& system) + { + NazaraAssert(system, "System must be valid"); + + SystemId systemId = system->GetId(); + + // Affectation et retour du système + m_systems[systemId] = std::move(system); + m_systems[systemId]->SetWorld(*this); + + MarkAllAsDirty(); // On force une mise à jour de toutes les entités + + return *m_systems[systemId].get(); + } + + template + SystemType& World::AddSystem(Args&&... args) + { + static_assert(std::is_base_of(), "SystemType is not a component"); + + // Allocation et affectation du component + std::unique_ptr ptr(new SystemType(std::forward(args)...)); + return static_cast(AddSystem(std::move(ptr))); + } + inline World::EntityList World::CreateEntities(unsigned int count) { EntityList list; @@ -17,6 +43,41 @@ namespace Ndk return list; } + inline BaseSystem& World::GetSystem(SystemId systemId) + { + ///DOC: Le système doit être présent + NazaraAssert(HasSystem(systemId), "This system is not part of the world"); + + BaseSystem* system = m_systems[systemId].get(); + NazaraAssert(system, "Invalid system pointer"); + + return *system; + } + + template + SystemType& World::GetSystem() + { + ///DOC: Le système doit être présent + static_assert(std::is_base_of(), "SystemType is not a system"); + + SystemId systemId = GetSystemId(); + return static_cast(GetSystem(systemId)); + } + + inline bool World::HasSystem(SystemId systemId) const + { + return m_systems.count(systemId) > 0; + } + + template + bool World::HasSystem() const + { + static_assert(std::is_base_of(), "SystemType is not a component"); + + SystemId systemId = GetSystemId(); + return HasSystem(systemId); + } + inline void World::KillEntities(const EntityList& list) { for (const EntityHandle& entity : list) @@ -32,4 +93,36 @@ namespace Ndk { return id < m_entities.size() && m_entities[id].entity.IsValid(); } + + inline void World::RemoveAllSystems() + { + m_systems.clear(); + } + + inline void World::RemoveSystem(SystemId systemId) + { + ///DOC: N'a aucun effet si le système n'est pas présent + if (HasSystem(systemId)) + m_systems[systemId].reset(); + } + + template + void World::RemoveSystem() + { + static_assert(std::is_base_of(), "SystemType is not a system"); + + SystemId systemId = GetSystemId(); + RemoveSystem(systemId); + } + + inline void World::MarkAllAsDirty() + { + m_dirtyEntities.Resize(m_entities.size(), false); + m_dirtyEntities.Set(true); // Activation de tous les bits + } + + inline void World::MarkAsDirty(EntityId id) + { + m_dirtyEntities.UnboundedSet(id, true); + } } diff --git a/SDK/src/NDK/BaseSystem.cpp b/SDK/src/NDK/BaseSystem.cpp new file mode 100644 index 000000000..d10b66e9d --- /dev/null +++ b/SDK/src/NDK/BaseSystem.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2015 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 + +namespace Ndk +{ + BaseSystem::~BaseSystem() + { + for (const EntityHandle& entity : m_entities) + entity->UnregisterSystem(m_systemId); + } + + bool BaseSystem::Filters(const EntityHandle& entity) const + { + for (ComponentId component : m_requiredComponents) + { + if (!entity->HasComponent(component)) + return false; // Au moins un component requis n'est pas présent + } + + for (ComponentId component : m_excludedComponents) + { + if (entity->HasComponent(component)) + return false; // Au moins un component exclu est présent + } + + return true; + } + + void BaseSystem::OnEntityAdded(const EntityHandle& entity) + { + NazaraUnused(entity); + } + + void BaseSystem::OnEntityRemoved(const EntityHandle& entity) + { + NazaraUnused(entity); + } +} diff --git a/SDK/src/NDK/Entity.cpp b/SDK/src/NDK/Entity.cpp index e3e499f6a..7558438ae 100644 --- a/SDK/src/NDK/Entity.cpp +++ b/SDK/src/NDK/Entity.cpp @@ -32,6 +32,9 @@ namespace Ndk // Affectation et retour du component m_components[componentId] = std::move(component); + // On informe le monde que nous avons besoin d'une mise à jour + m_world->MarkAsDirty(m_id); + return *m_components[componentId].get(); } @@ -53,13 +56,21 @@ namespace Ndk void Entity::RemoveAllComponents() { m_components.clear(); + + // On informe le monde que nous avons besoin d'une mise à jour + m_world->MarkAsDirty(m_id); } void Entity::RemoveComponent(ComponentId componentId) { ///DOC: N'a aucun effet si le component n'est pas présent if (HasComponent(componentId)) + { m_components[componentId].reset(); + + // On informe le monde que nous avons besoin d'une mise à jour + m_world->MarkAsDirty(m_id); + } } void Entity::Create() @@ -71,6 +82,17 @@ namespace Ndk { m_valid = false; + // On informe chaque système + for (SystemId systemId : m_systems) + { + if (m_world->HasSystem(systemId)) + { + BaseSystem& system = m_world->GetSystem(systemId); + system.RemoveEntity(CreateHandle()); + } + } + m_systems.clear(); + // On informe chaque handle de notre destruction pour éviter qu'il ne continue de pointer sur nous for (EntityHandle* handle : m_handles) handle->OnEntityDestroyed(); diff --git a/SDK/src/NDK/World.cpp b/SDK/src/NDK/World.cpp index a04c1f4d2..65125d568 100644 --- a/SDK/src/NDK/World.cpp +++ b/SDK/src/NDK/World.cpp @@ -106,5 +106,41 @@ namespace Ndk m_aliveEntities.pop_back(); } m_killedEntities.Reset(); + + for (unsigned int i = m_dirtyEntities.FindFirst(); i != m_dirtyEntities.npos; i = m_dirtyEntities.FindNext(i)) + { + NazaraAssert(i < m_entities.size(), "Entity index out of range"); + + EntityBlock& block = m_entities[i]; + Entity& entity = block.entity; + EntityHandle& handle = m_aliveEntities[block.aliveIndex]; + + // Aucun intérêt de traiter une entité n'existant plus + if (entity.IsValid()) + { + for (auto& systemPair : m_systems) + { + BaseSystem* system = systemPair.second.get(); + + // L'entité est-elle enregistrée comme faisant partie du système ? + bool partOfSystem = system->HasEntity(handle); + if (system->Filters(handle)) + { + // L'entité doit faire partie du système, est-ce que c'est déjà le cas ? + if (!partOfSystem) + // Non, rajoutons-là + system->AddEntity(handle); + } + else + { + // L'entité ne doit pas faire partie du système, était-ce le cas ? + if (partOfSystem) + // Oui, enlevons-là + system->RemoveEntity(handle); + } + } + } + } + m_dirtyEntities.Reset(); } }