Skip to content

Commit

Permalink
Testing range-based iteration of components
Browse files Browse the repository at this point in the history
  • Loading branch information
jadebenn committed Jan 22, 2024
1 parent 7b17793 commit b802c13
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 28 deletions.
2 changes: 1 addition & 1 deletion dGame/dEntity/Archetype.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class IArchetype {
/**
* Create an alias type for the Archetype ID
*/
using ArchetypeId = uint32_t;
using ArchetypeId = size_t;
ArchetypeId id{ 0 };

/**
Expand Down
53 changes: 26 additions & 27 deletions dGame/dEntity/EntitySystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ namespace {
* The operator() for the ComponentVisitor struct. Used to get components from an archetype
* @returns A const pointer to the component if it is present in the archetype, nullptr if it is not
*/
constexpr CType* const operator()(auto&& archetype) { // TODO: There might be a way to use this to do compile-time checking...
using ArchetypeType = std::remove_pointer_t<std::remove_reference_t<decltype(archetype)>>;
constexpr CType* const operator()(auto&& archetype) {
using ArchetypeType = std::remove_pointer_t<std::remove_reference_t<decltype(*archetype)>>; // Needed to fix a MacOS issue

if constexpr (ArchetypeType::template HasComponent<CType>()) {
return &archetype->template GetComponent<CType>(index);
Expand All @@ -55,14 +55,14 @@ class EntitySystem final {
using ArchetypeId = uint32_t;
using ArchetypeSet = std::unordered_set<ArchetypeId>;
using ArchetypeVariantPtr = std::variant<
Archetype<CharacterComponent>*,
Archetype<DestroyableComponent>*,
Archetype<SimplePhysicsComponent>*,
std::unique_ptr<Archetype<CharacterComponent>>,
std::unique_ptr<Archetype<DestroyableComponent>>,
std::unique_ptr<Archetype<SimplePhysicsComponent>>,

Archetype<CharacterComponent, DestroyableComponent>*,
Archetype<DestroyableComponent, SimplePhysicsComponent>*,
std::unique_ptr<Archetype<CharacterComponent, DestroyableComponent>>,
std::unique_ptr<Archetype<DestroyableComponent, SimplePhysicsComponent>>,

Archetype<CharacterComponent, DestroyableComponent, SimplePhysicsComponent>*
std::unique_ptr<Archetype<CharacterComponent, DestroyableComponent, SimplePhysicsComponent>>
>; // TODO: Figure out how to generate this automatically
using ComponentTypeId = std::type_index;

Expand All @@ -81,7 +81,7 @@ class EntitySystem final {

const size_t insertedIndex = archetype.size();
archetype.CreateComponents(std::forward<CTypes>(componentArgs)...); // Create the components in the archetype
m_EntityIndex.try_emplace(explicitId, ArchetypeRecord{ &archetype, insertedIndex }); // Create the corresponding pointers in the entity index
m_EntityIndex.try_emplace(explicitId, ArchetypeRecord{ archetype.id, insertedIndex }); // Create the corresponding pointers in the entity index
}

/**
Expand All @@ -107,7 +107,7 @@ class EntitySystem final {

/**
* Determine if an entity is associated with an Object ID
*
* @returns A boolean representing whether the entity exists
*/
[[nodiscard]] bool EntityExists(const LWOOBJID entityId) noexcept {
return m_EntityIndex.count(entityId) != 0;
Expand All @@ -120,10 +120,10 @@ class EntitySystem final {
*/
template <ComponentType CType>
[[nodiscard]] bool HasComponent(const LWOOBJID entityId) {
const auto& archetypeRecord = m_EntityIndex[entityId];
const ArchetypeRecord& record = m_EntityIndex[entityId];
const auto& hasComponentVisitor = [](auto&& archetype) { return archetype->template HasComponent<CType>(); };

return std::visit(hasComponentVisitor, archetypeRecord.archetypePtr); // Using visitor pattern
return std::visit(hasComponentVisitor, m_Archetypes[record.archetypeIndex]); // Using visitor pattern
}

/**
Expand All @@ -133,9 +133,9 @@ class EntitySystem final {
*/
template <ComponentType CType>
[[nodiscard]] CType* const GetComponent(const LWOOBJID entityId) {
const auto& archetypeRecord = m_EntityIndex[entityId];
const ArchetypeRecord& record = m_EntityIndex[entityId];

return std::visit(ComponentVisitor<CType>{ archetypeRecord.index }, archetypeRecord.archetypePtr); // Using visitor pattern
return std::visit(ComponentVisitor<CType>(record.componentIndex), m_Archetypes[record.archetypeIndex]); // Using visitor pattern
}

protected:
Expand All @@ -144,35 +144,34 @@ class EntitySystem final {
* @param archetypeId The ID to assign to the created archetype
*/
template <ComponentType... CTypes>
[[nodiscard]] Archetype<CTypes...> CreateArchetype(const ArchetypeId archetypeId) { // TODO: Noexcept?
(m_ComponentTypeIndex[std::type_index(typeid(CTypes))].insert(archetypeId), ...); // Add the matching types to the component type index
return Archetype<CTypes...>{ archetypeId }; // Return the created archetype
[[nodiscard]] const size_t CreateArchetype() { // TODO: Noexcept?
const size_t indexToInsert = m_Archetypes.size();
m_Archetypes.emplace_back(std::make_unique<Archetype<CTypes...>>(indexToInsert));
return indexToInsert;
}

/**
* Method to get a reference to an entity archetype given the components it contains
*/
template <ComponentType... CTypes>
[[nodiscard]] Archetype<CTypes...>& GetArchetype() {
static auto archetype = CreateArchetype<CTypes...>(++m_CurrentArchetypeId);
return archetype;
}
static size_t archetypeIndex = CreateArchetype<CTypes...>(); // TODO: Maybe split this out into some kind of 'register' function instead?

private:
friend class ArchetypeTest;
return *std::get<std::unique_ptr<Archetype<CTypes...>>>(m_Archetypes[archetypeIndex]);
}

ArchetypeId m_CurrentArchetypeId{ 0 };
public:
//private:
std::vector<ArchetypeVariantPtr> m_Archetypes;

struct ArchetypeRecord {
ArchetypeVariantPtr archetypePtr;
size_t index;
size_t archetypeIndex;
size_t componentIndex;
};

std::unordered_map<LWOOBJID, ArchetypeRecord> m_EntityIndex;

std::unordered_set<LWOOBJID> m_EntitiesToDelete;

std::unordered_map<ComponentTypeId, ArchetypeSet> m_ComponentTypeIndex;
};

#endif // !__ENTITYSYSTEM_H
91 changes: 91 additions & 0 deletions tests/dGameTests/dEntitiesTests/ArchetypeTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,94 @@ TEST_F(ArchetypeTest, GetComponentTest) {
ASSERT_NE(entitySystem->GetComponent<SimplePhysicsComponent>(baseEntityId), nullptr);
ASSERT_EQ(entitySystem->GetComponent<CharacterComponent>(baseEntityId), nullptr);
}

namespace {
template <typename T>
struct TestContainerVisitor {
void operator()(auto&& archetype) {
using ArchetypeType = std::remove_pointer_t<std::remove_reference_t<decltype(*archetype)>>; // Needed to fix a MacOS issue

if constexpr (!ArchetypeType::template HasComponent<T>()) return;
else for (auto& destComp : archetype->template Container<T>()) {
const auto randNum = rand();
destComp.SetArmor(randNum);
ASSERT_EQ(randNum, destComp.GetArmor());
}
}
};
}

TEST_F(ArchetypeTest, IterateOverArchetypesTest) {
auto entitySystem = std::make_unique<EntitySystem>();

const auto baseEntityId = baseEntity->GetObjectID();
entitySystem->CreateEntity(baseEntityId, DestroyableComponent(baseEntityId), SimplePhysicsComponent(baseEntityId, 2));

const auto newEntityId = newEntity->GetObjectID();
entitySystem->CreateEntity(newEntityId, DestroyableComponent(newEntityId), SimplePhysicsComponent(newEntityId, 1));

const auto it = std::ranges::find_if(entitySystem->m_Archetypes, [](auto&& archetypeVariantPtr) {

Check failure on line 296 in tests/dGameTests/dEntitiesTests/ArchetypeTests.cpp

View workflow job for this annotation

GitHub Actions / Build & Test (macos-11)

no member named 'ranges' in namespace 'std'
return std::visit([](auto&& archetype) {
return archetype->template HasComponent<DestroyableComponent>();
}, archetypeVariantPtr);
});
ASSERT_FALSE(it == entitySystem->m_Archetypes.end());

// ------------------- UPDATE LOOP TEST --------------
std::vector<std::unique_ptr<Entity>> tempEntity; // Vector of temporary entities (so they die when this test goes out of scope)

constexpr int32_t numEntries = 1000000;

LOG("Number of entries per vector: %d", numEntries);
srand(time(NULL));
for (auto i = 0; i < numEntries; ++i) {
tempEntity.emplace_back(std::make_unique<Entity>(rand() + i, GameDependenciesTest::info)); // Create a new entity

const auto tempEntityId = tempEntity[i]->GetObjectID();
entitySystem->CreateEntity(tempEntityId, DestroyableComponent(tempEntityId), SimplePhysicsComponent(tempEntityId, 2));
}

//ContainerVisitor
const auto& archetypes = entitySystem->m_Archetypes;

for (auto& archetypeVariantPtr : archetypes) {
std::visit(TestContainerVisitor<DestroyableComponent>(), archetypeVariantPtr); // Does the update loop test
}

/*for (auto& archetypeVariantPtr : archetypes) { // For the archetypes in m_Archetypes
const bool archetypeHasComponent = std::visit([](auto&& archetype), archetypeVariantPtr);
if (!archetypeHasComponent) continue; // Skip archetypes that don't have the relevant component
auto&& destCompCont = std::visit([](auto&& archetype) { return archetype->template Container<DestroyableComponent>(); }, archetypeVariantPtr); // Get destroyable component container
for (auto& destComp : destCompCont) {
const auto randNum = rand();
destComp.SetArmor(randNum);
ASSERT_EQ(randNum, destComp.GetArmor());
}
}*/

// Expanded version
/*for (auto it = archetypes.begin(), end = archetypes.end(); it != end; ++it) {
const auto& archetypeVariantPtr = *it;
std::vector<DestroyableComponent>&& destCompCont = std::visit([](auto&& archetype){ return archetype->template Container<DestroyableComponent>(); }, archetypeVariantPtr);
for (auto it = destCompCont.begin(), end = destCompCont.end(); it != end; ++it) {
const auto& destComp = *it;
// Magic
}
}*/

// Notation I WANT
/*for (auto& archetype : archetypes) {
for (auto& destCompCont : archetype) {
for (auto& destComp : destCompCont) {
// MAGIC
}
}
}*/

ASSERT_NO_THROW(entitySystem->GetComponent<DestroyableComponent>(baseEntityId)->GetArmor());
ASSERT_NO_THROW(entitySystem->GetComponent<DestroyableComponent>(newEntityId)->GetArmor());
};

0 comments on commit b802c13

Please sign in to comment.