testing iteration

This commit is contained in:
jadebenn 2024-12-17 22:06:41 -06:00
parent 93dcfddac5
commit 85eb5a7261
6 changed files with 235 additions and 25 deletions

View File

@ -3,6 +3,7 @@ set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
add_library(dECS STATIC
"Core.h"
"Iter.h"
"Core.cpp"
)
target_include_directories(dECS PUBLIC .)

View File

@ -1,6 +1,8 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
@ -12,8 +14,13 @@ namespace dECS {
// template <typename C>
// concept IsComponent = std::derived_from<C, Component>;
// Data structures
struct WorldData;
class World;
template <typename... Cs>
class System;
class Entity;
struct IStorage;
@ -30,10 +37,59 @@ namespace dECS {
[[nodiscard]]
Entity MakeEntity();
template <typename... Cs>
[[nodiscard]]
System<Cs...> MakeSystem() {
return System<Cs...>{};
}
template <typename... Cs, typename S>
[[nodiscard]]
System<Cs...> MakeSystem(S&& name) {
return System<Cs...>{ std::forward<S>(name) };
}
private:
WorldPtr m_World;
};
template <typename... Cs>
class System {
public:
friend System World::MakeSystem<Cs...>();
template <typename... Ts, typename S>
friend System<Ts...> World::MakeSystem(S&&);
/*template <typename Fn>
requires std::is_invocable_r_v<void, Fn(Cs...), ObjId, Cs...>
void ForEach(Fn&& f) {
for (ObjId i = 0; i < mT.size(); ++i) {
auto& c = mT[i];
f(i, std::get<Cs>(c)...);
}
}*/
template <typename Fn>
requires std::is_invocable_r_v<void, Fn(Cs...), Cs...>
void ForEach(Fn&& fn) {
std::tuple<Cs...> comps; // some sort of iterator that returns a tuple each 'step?'
for (size_t i = 0; i < 5; ++i) {
fn(std::get<Cs>(comps)...);
}
}
private:
System() = default;
template <typename S>
explicit System(S&& name)
: m_name{ std::forward<S>(name) }
{}
std::string m_name;
};
class Entity {
public:
friend Entity World::MakeEntity();

12
dECS/Iter.h Normal file
View File

@ -0,0 +1,12 @@
#include "Core.h"
namespace dECS {
class Iter {
public:
[[nodiscard]]
bool Next();
private:
WeakWorldPtr m_World;
};
}

View File

@ -1,18 +1,5 @@
#include "Component.h"
Component::Component(Entity* parent) {
m_Parent = parent;
}
Component::~Component() {
}
Entity* Component::GetParent() const {
return m_Parent;
}
void Component::Update(float deltaTime) {
}

View File

@ -13,14 +13,15 @@ class Entity;
*/
class Component {
public:
Component(Entity* parent);
virtual ~Component();
Component() = default;
Component(Entity* parent) : m_Parent{ parent } {}
virtual ~Component() = default;
/**
* Gets the owner of this component
* @return the owner of this component
*/
Entity* GetParent() const;
Entity* GetParent() const { return m_Parent; }
/**
* Updates the component in the game loop

View File

@ -1,3 +1,6 @@
#include <cstdint>
#include <cstddef>
#include <span>
#include <optional>
#include <gtest/gtest.h>
#include "Core.h"
@ -6,11 +9,122 @@
using namespace dECS;
struct TestComponent {
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CHOICE_BUILD;
namespace TestECS {
using LegacyComponent = ::Component;
int value;
};
namespace Component {
using enum eReplicaComponentType;
void* NULL_PARENT = nullptr;
struct Legacy : public LegacyComponent {
static constexpr eReplicaComponentType ComponentType = CHANGLING;
Legacy() = default;
void Update(float deltaTime) {
std::printf("Legacy updated!\n");
}
};
struct Invalid {
static constexpr eReplicaComponentType ComponentType = INVALID;
int value;
};
struct Destroyable {
static constexpr eReplicaComponentType ComponentType = DESTROYABLE;
using FactionId = int32_t;
float health;
float maxHealth;
float armor;
float maxArmor;
float imag;
float maxImag;
uint32_t damageToAbsorb;
bool immune;
bool gmImmune;
bool shielded;
float actualMaxHealth;
float actualMaxArmor;
float actualMaxImagination;
std::vector<FactionId> factionIds;
bool smashable;
};
}
struct IFakeSystem {
virtual ~IFakeSystem() = default;
[[nodiscard]]
constexpr virtual size_t Count() const noexcept = 0;
constexpr virtual void EmplaceBack() = 0;
};
template <typename... Cs>
struct FakeSystem : public IFakeSystem {
template <typename C>
using Storage = std::vector<std::remove_const_t<C>>;
std::tuple<Storage<Cs>...> data;
[[nodiscard]]
constexpr size_t Count() const noexcept override {
return std::get<0>(data).size();
}
constexpr void EmplaceBack() override {
(std::get<Storage<Cs>>(data).emplace_back(), ...);
}
template <typename C>
requires std::disjunction_v<std::is_same<C, Cs>...>
[[nodiscard]]
std::span<C> Get() {
return std::get<Storage<C>>(data);
}
template <typename C>
requires std::disjunction_v<std::is_same<C, Cs>...>
[[nodiscard]]
std::span<const C> Get() const {
return std::get<Storage<C>>(data);
}
template <typename Fn>
requires std::is_invocable_r_v<void, Fn(Cs...), Cs...>
void ForEach(Fn&& fn);
};
class FakeIter {
public:
constexpr FakeIter(const IFakeSystem& fakeSys) noexcept
: m_System{ fakeSys }
{}
[[nodiscard]]
constexpr bool Next() {
return m_Count++ > m_System.Count();
}
private:
size_t m_Count;
const IFakeSystem& m_System;
};
template <typename... Cs>
template <typename Fn>
requires std::is_invocable_r_v<void, Fn(Cs...), Cs...>
void FakeSystem<Cs...>::ForEach(Fn&& fn){
for (size_t i = 0; i < Count(); ++i) {
fn(Get<Cs>()[i]...);
}
}
}
// Test that entity IDs increment correctly
TEST(ECSTest, IncrementEntityIdsSingleThread) {
@ -28,19 +142,21 @@ TEST(ECSTest, IncrementEntityIdsSingleThread) {
// Test adding and getting components
TEST(ECSTest, MakeOneEntityAndAddComponents) {
using namespace TestECS::Component;
auto w = World{};
auto e = w.MakeEntity();
ASSERT_EQ(e.GetObjectID(), 1);
// add component
auto* const testCompPtr = e.AddComponent<TestComponent>();
auto* const testCompPtr = e.AddComponent<Invalid>();
ASSERT_NE(testCompPtr, nullptr);
ASSERT_EQ(testCompPtr->ComponentType, eReplicaComponentType::CHOICE_BUILD);
ASSERT_EQ(testCompPtr->ComponentType, Invalid::ComponentType);
ASSERT_EQ(testCompPtr->value, 0);
testCompPtr->value = 15;
// try getting the same component we just added
auto* const gotTestCompPtr = e.GetComponent<TestComponent>();
auto* const gotTestCompPtr = e.GetComponent<Invalid>();
ASSERT_NE(gotTestCompPtr, nullptr);
ASSERT_EQ(gotTestCompPtr, testCompPtr);
ASSERT_NE(gotTestCompPtr->value, 0);
@ -49,6 +165,8 @@ TEST(ECSTest, MakeOneEntityAndAddComponents) {
// Test world scoping
TEST(ECSTest, WorldScope) {
using namespace TestECS::Component;
auto e = std::optional<dECS::Entity>{};
{
@ -57,11 +175,46 @@ TEST(ECSTest, WorldScope) {
ASSERT_EQ(e->GetObjectID(), 1);
// add component within scope
auto* const cPtr = e->AddComponent<TestComponent>();
auto* const cPtr = e->AddComponent<Invalid>();
ASSERT_NE(cPtr, nullptr);
}
// Attempting to access this component should return nullptr
// now that the world has gone out of scope
ASSERT_EQ(e->GetComponent<TestComponent>(), nullptr);
ASSERT_EQ(e->GetComponent<Invalid>(), nullptr);
}
// Create and iterate over a system
TEST(ECSTest, CreateAndIterateOverSystem) {
using namespace TestECS::Component;
auto w = World{};
auto s = w.MakeSystem<Destroyable, const Invalid>("DestInvalid");
size_t count = 0;
s.ForEach([&](Destroyable& d, const Invalid& i) {
std::printf("i = %ld: d.health = %f\n", ++count, d.health);
d.health += 1;
});
}
TEST(ECSTest, FakeIterationForTestingPurposes) {
using namespace TestECS;
using namespace TestECS::Component;
auto s = FakeSystem<Legacy, Destroyable>{};
auto const r = 2 + std::rand() % 8;
for (size_t i = 0; i < r; ++i) {
s.EmplaceBack();
}
size_t count = 0;
s.ForEach([&](Legacy& l, Destroyable& d) {
l.Update(0.0f);
std::printf("i = %ld: d.health = %f\n", ++count, d.health);
d.health += 1;
});
std::printf("Total count = %ld\n", count);
ASSERT_EQ(r, count);
}