From 59dc667411f78947b86c4141fbcf0560c306246c Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Mon, 4 Sep 2023 00:47:37 +0200 Subject: [PATCH 01/19] [SYNC] --- CMakeLists.txt | 4 +- projs/experiments/ecs/CMakeLists.txt | 22 + projs/experiments/ecs/src/ecs.exp.h | 315 ++++++++++ projs/experiments/ecs/src/main.cpp | 19 + .../ecs/tests/span_dynamic.test.cpp | 87 +++ projs/experiments/ecs/tests/tests.cpp | 3 + .../shadow/shadow-engine/core/CMakeLists.txt | 2 +- .../shadow-engine/entity/CMakeLists.txt | 5 + .../entity/inc/shadow/entitiy/EntitySystem.h | 37 +- .../entity/inc/shadow/entitiy/NodeContainer.h | 554 +++++++++--------- .../entity/inc/shadow/entitiy/NodeManager.h | 15 - .../entity/inc/shadow/entitiy/SystemManager.h | 2 +- .../inc/shadow/entitiy/entities/Light.h | 32 +- .../shadow/entitiy/entities/MeshComponent.h | 18 +- .../inc/shadow/entitiy/entities/NullActor.h | 16 +- .../inc/shadow/entitiy/entities/Position.h | 22 +- .../inc/shadow/entitiy/graph/NodeFunctions.h | 8 + .../inc/shadow/entitiy/graph/archetype.h | 36 ++ .../entity/inc/shadow/entitiy/graph/graph.h | 508 ---------------- .../inc/shadow/entitiy/graph/managers.h | 256 ++++++++ .../entity/inc/shadow/entitiy/graph/nodes.h | 215 +++++++ .../shadow-engine/entity/src/NodeManager.cpp | 135 ++--- .../entity/src/debug/AllocationDebugger.cpp | 6 +- .../shadow-engine/entity/src/graph/graph.cpp | 129 ++-- .../entity/src/graph/managers.cpp | 77 +++ .../entity/tests/managers.test.cpp | 14 + .../reflection/inc/shadow/SHObject.h | 13 +- .../utility/inc/shadow/util/UUID.h | 72 +++ .../shadow/shadow-engine/utility/src/UUID.cpp | 56 ++ projs/test-game/inc/TestScene.h | 10 +- projs/test-game/inc/entities/Health.h | 8 +- projs/test-game/inc/entities/Player.h | 14 +- projs/test-game/inc/entities/TestCamera.h | 10 +- 33 files changed, 1682 insertions(+), 1038 deletions(-) create mode 100644 projs/experiments/ecs/CMakeLists.txt create mode 100644 projs/experiments/ecs/src/ecs.exp.h create mode 100644 projs/experiments/ecs/src/main.cpp create mode 100644 projs/experiments/ecs/tests/span_dynamic.test.cpp create mode 100644 projs/experiments/ecs/tests/tests.cpp delete mode 100644 projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeManager.h create mode 100644 projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/NodeFunctions.h create mode 100644 projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/archetype.h delete mode 100644 projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/graph.h create mode 100644 projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/managers.h create mode 100644 projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/nodes.h create mode 100644 projs/shadow/shadow-engine/entity/src/graph/managers.cpp create mode 100644 projs/shadow/shadow-engine/entity/tests/managers.test.cpp create mode 100644 projs/shadow/shadow-engine/utility/inc/shadow/util/UUID.h create mode 100644 projs/shadow/shadow-engine/utility/src/UUID.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8070d769..b31f47df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,4 +16,6 @@ set(CMAKE_SHARED_LIBRARY_PREFIX "") add_subdirectory(projs/shadow) -add_subdirectory(projs/test-game) \ No newline at end of file +add_subdirectory(projs/test-game) + +add_subdirectory(projs/experiments/ecs) \ No newline at end of file diff --git a/projs/experiments/ecs/CMakeLists.txt b/projs/experiments/ecs/CMakeLists.txt new file mode 100644 index 00000000..b6bc3152 --- /dev/null +++ b/projs/experiments/ecs/CMakeLists.txt @@ -0,0 +1,22 @@ +set(CMAKE_CXX_STANDARD 20) + +add_executable(experiment-ecs) +add_executable(experiment::ecs ALIAS experiment-ecs) + + +FILE(GLOB_RECURSE SOURCES + src/*.cpp +) + +target_sources(experiment-ecs PUBLIC ${SOURCES}) + +FILE(GLOB_RECURSE SOURCES_TESTS + tests/*.cpp +) + +add_executable(experiment-ecs-tests) +target_link_libraries(experiment-ecs-tests + Catch2::Catch2 +) +target_sources(experiment-ecs-tests PUBLIC ${SOURCES_TESTS}) +target_include_directories(experiment-ecs-tests PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src) \ No newline at end of file diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h new file mode 100644 index 00000000..7caa22eb --- /dev/null +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -0,0 +1,315 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "../../shadow/shadow-engine/reflection/inc/shadow/SHObject.h" +//#include "../../shadow/shadow-engine/core/inc/shadow/exports.h" + +namespace SH { + + class span_dynamic { + std::byte *p_start; + std::byte *p_end; + size_t itemSize; + public: + span_dynamic() = default; + span_dynamic(void *mem, size_t item_size, size_t count) : + p_start(static_cast(mem)), + p_end(p_start + (item_size * count)), + itemSize(item_size) { + assert((p_end - p_start) / item_size == count); + } + + public: + class iterator { + public: + using difference_type = std::ptrdiff_t; + using value_type = std::byte; + private: + std::byte *pos; + size_t size; + + public: + iterator() : pos(nullptr), size(0) {}; + iterator(std::byte *p, size_t item_size) : pos(p), size(item_size) {}; + + void move(size_t n) { + pos += (n * size); + } + + iterator &operator++() { + move(1); + return *this; + } + iterator operator++(int) { + iterator tmp(*this); + operator++(); + return tmp; + } + + iterator &operator--() { + move(-1); + return *this; + } + iterator operator--(int) { + iterator tmp(*this); + operator--(); + return tmp; + } + + iterator &operator+=(difference_type n) { + move(n); + return *this; + } + iterator &operator-=(difference_type n) { + move(-n); + return *this; + } + + iterator operator+(const difference_type &n) const { + iterator tmp(*this); + tmp.move(n); + return tmp; + } + iterator operator-(const difference_type &n) const { + iterator tmp(*this); + tmp.move(-n); + return tmp; + } + + std::byte &operator[](const difference_type &n) const { + iterator tmp(*this); + tmp.move(n); + return *tmp; + } + + difference_type operator-(const iterator &rhs) const { return pos - rhs.pos; } + + bool operator==(const iterator &rhs) const { return pos == rhs.pos; } + bool operator!=(const iterator &rhs) const { return pos != rhs.pos; } + + bool operator<(const iterator &rhs) const { return pos < rhs.pos; } + bool operator<=(const iterator &rhs) const { return pos <= rhs.pos; } + bool operator>(const iterator &rhs) const { return pos > rhs.pos; } + bool operator>=(const iterator &rhs) const { return pos >= rhs.pos; } + + std::byte &operator*() const { return *pos; } + + template + T &as() { return *(T *) pos; } + template + T *as_ptr() { return (T *) pos; } + + void *ptr() const { return pos; } + }; + + iterator begin() const { + return {p_start, itemSize}; + } + + iterator end() const { + return {p_end, itemSize}; + } + + iterator last() const { + return end()--; + } + }; + + span_dynamic::iterator operator+(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { + span_dynamic::iterator tmp(i); + tmp.move(n); + return tmp; + } + span_dynamic::iterator operator-(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { + span_dynamic::iterator tmp(i); + tmp.move(-n); + return tmp; + } + + bool operator<(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() < rhs; } + bool operator<(const void *lhr, const span_dynamic::iterator &rhs) { return lhr < rhs.ptr(); } + bool operator<=(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() <= rhs; } + bool operator>(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() > rhs; } + bool operator>(const void *lhr, const span_dynamic::iterator &rhs) { return lhr > rhs.ptr(); } + bool operator>=(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() >= rhs; } + bool operator>=(const void *lhr, const span_dynamic::iterator &rhs) { return lhr >= rhs.ptr(); } +} + +static_assert(std::input_or_output_iterator); +static_assert(std::random_access_iterator); + +static_assert(std::ranges::random_access_range); + +namespace SH { + + class PoolAllocator { + public: + + struct Item { + Item *next; + }; + + explicit PoolAllocator(size_t item_size) : item_size(std::max(item_size, sizeof(Item))) { + chunks.push_back(new Chunk(item_size)); + } + + class Chunk { + span_dynamic memory; //= span_dynamic(nullptr, 0, 0); + Item *next_free = nullptr; + public: + explicit Chunk(size_t item_size) { + void *m = malloc(item_size * 1024); + memory = span_dynamic(m, item_size, 1024); + + next_free = memory.begin().as_ptr(); + for (auto it = memory.begin(); it != memory.end(); it++) { + auto next = it + 1; + it.as_ptr()->next = next.as_ptr(); + } + memory.last().as_ptr()->next = nullptr; + } + + bool HasSpace() { return next_free != nullptr; } + + void *allocate() { + assert(next_free != nullptr); + auto ptr = next_free; + next_free = next_free->next; + return ptr; + } + + void deallocate(void *p) { + assert(contains(p)); + auto item = static_cast(p); + item->next = next_free; + next_free = item; + } + + bool contains(void *p) { + return p >= memory.begin() && p < memory.end(); + } + }; + + public: + + virtual void *allocate() { + auto chunk_it = std::ranges::find_if(chunks, [](auto chunk) { return chunk->HasSpace(); }); + + // If no chunk has space, create a new one + if (chunk_it == chunks.end()) { + chunks.push_back(new Chunk(item_size)); + chunk_it = std::prev(chunks.end()); + } + + return (*chunk_it)->allocate(); + }; + + virtual void deallocate(void *p) { + auto chunk_it = std::ranges::find_if(chunks, [p](auto chunk) { return chunk->contains(p); }); + if (chunk_it == chunks.end()) + throw std::invalid_argument("WTF man common use pointer correctly"); + + (*chunk_it)->deallocate(p); + }; + private: + std::vector chunks; + size_t item_size; + }; + +} + +/* + * Archetype : (T1, T2, T3, T4) + * | self (T1) | comp 1 (T2) | comp 2 (T3) | comp 3 (T4) | + * | T1: 1 | T2: 1 | T3: 1 | T4: 1 | + * | T1: 2 | T2: 2 | T3: 2 | T4: 2 | + * | T1: 3 | T2: 3 | T3: 3 | T4: 3 | + * + * Archetype : (T1, T2, T3) + * | self (T1) | comp 1 (T2) | comp 2 (T3) | + * | T1: 4 | T2: 4 | T3: 4 | + * | T1: 5 | T2: 5 | T3: 5 | + * + * + */ +class Object { + +}; + +class Entity { + +}; +template +concept entity = std::is_base_of_v; + +union Id { + std::byte bytes[sizeof(uint64_t)]; + uint64_t id; +}; + +using TypeId = Id; + +template<> +struct std::hash { + std::size_t operator()(const TypeId &s) const noexcept { + return std::hash{}(s.id); + }; +}; + +TypeId next_id; + +template +TypeId getTypeId() { + static TypeId id = next_id; + id.id++; + return id; +} + +class Archetype { + using Id = uint32_t; + static Id next_id; + + std::vector types; + Id id; +public: + Archetype() = default; + Archetype(std::initializer_list types) : types(types), id(next_id++) { + + } +}; + +class EntityManager { + + std::unordered_map pools = {}; + + template + SH::PoolAllocator &GetPool(TypeId type) { + if (!pools.contains(type)) { + pools.insert({type, SH::PoolAllocator(sizeof(Ent))}); + } + return pools.at(type); + } + + template + Ent *AddChild(Entity &parent) { + // Allocate the space for the entity + SH::PoolAllocator &pool = GetPool(getTypeId()); + void *pos = pool.allocate(); + + // Create it + Ent *entity = new(pos)Ent(); + + // Add it to the parent + return entity; + } + +}; + +Archetype::Id Archetype::next_id = 0; \ No newline at end of file diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp new file mode 100644 index 00000000..071efa57 --- /dev/null +++ b/projs/experiments/ecs/src/main.cpp @@ -0,0 +1,19 @@ +// +// Created by dpeter99 on 2023.09.10.. +// +#include "ecs.exp.h" + +class Player{ + +}; + +class Position { + +}; + + +int main() { + EntityManager em; + + em.AddChild<>() +} \ No newline at end of file diff --git a/projs/experiments/ecs/tests/span_dynamic.test.cpp b/projs/experiments/ecs/tests/span_dynamic.test.cpp new file mode 100644 index 00000000..e0961b56 --- /dev/null +++ b/projs/experiments/ecs/tests/span_dynamic.test.cpp @@ -0,0 +1,87 @@ +#include "catch2/catch.hpp" + +#include "ecs.exp.h" + +struct TestObject { + int32_t a = 1; + int32_t b = 2; + int32_t c = 3; + int32_t d = 4; +}; + +TEST_CASE("span_dynamic tests") { + SECTION("Initialization and iteration") { + int arr[5] = {1, 2, 3, 4, 5}; + SH::span_dynamic const span(arr, sizeof(int), 5); + + int i = 1; + for (auto it = span.begin(); it != span.end(); ++it) { + REQUIRE(it.as() == i++); + } + } + + SECTION("Random access") { + int arr[5] = {1, 2, 3, 4, 5}; + SH::span_dynamic const span(arr, sizeof(int), 5); + + auto it = span.begin(); + it += 2; + REQUIRE(it.as() == 3); + } +} + +TEST_CASE("PoolAllocator tests") { + SECTION("Basic allocation") { + SH::PoolAllocator pool(sizeof(TestObject)); + auto obj = static_cast(pool.allocate()); + + REQUIRE(obj != nullptr); + } + + SECTION("Multiple allocations within a single chunk") { + SH::PoolAllocator pool(sizeof(TestObject)); + std::vector objs; + + for (int i = 0; i < 1024; ++i) { + auto obj = static_cast(pool.allocate()); + REQUIRE(obj != nullptr); + objs.push_back(obj); + } + } + + SECTION("Multiple allocations across multiple chunks") { + SH::PoolAllocator pool(sizeof(TestObject)); + std::vector objs; + + // Assuming that each chunk can hold 1024 items, + // this will require at least 2 chunks. + for (int i = 0; i < 2048; ++i) { + auto obj = static_cast(pool.allocate()); + REQUIRE(obj != nullptr); + objs.push_back(obj); + } + } + + SECTION("Allocation and deallocation") { + SH::PoolAllocator pool(sizeof(TestObject)); + auto obj1 = static_cast(pool.allocate()); + auto obj2 = static_cast(pool.allocate()); + + REQUIRE(obj1 != nullptr); + REQUIRE(obj2 != nullptr); + + pool.deallocate(obj1); + pool.deallocate(obj2); + + // After deallocation, new allocations should reuse the freed memory + auto obj3 = static_cast(pool.allocate()); + auto obj4 = static_cast(pool.allocate()); + + REQUIRE(obj3 != nullptr); + REQUIRE(obj4 != nullptr); + + // The addresses should be reused in reverse order + REQUIRE(obj3 == obj2); + REQUIRE(obj4 == obj1); + } +} \ No newline at end of file diff --git a/projs/experiments/ecs/tests/tests.cpp b/projs/experiments/ecs/tests/tests.cpp new file mode 100644 index 00000000..58d82872 --- /dev/null +++ b/projs/experiments/ecs/tests/tests.cpp @@ -0,0 +1,3 @@ +#define CATCH_CONFIG_MAIN +#include + diff --git a/projs/shadow/shadow-engine/core/CMakeLists.txt b/projs/shadow/shadow-engine/core/CMakeLists.txt index 2b2b27e6..6c9c81f8 100644 --- a/projs/shadow/shadow-engine/core/CMakeLists.txt +++ b/projs/shadow/shadow-engine/core/CMakeLists.txt @@ -5,8 +5,8 @@ FILE(GLOB_RECURSE SOURCES SET(TESTS ${CMAKE_CURRENT_LIST_DIR}/tests/PathID.test.cpp - ) + target_shadow_module(shadow-engine SOURCES ${SOURCES} INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/inc/ diff --git a/projs/shadow/shadow-engine/entity/CMakeLists.txt b/projs/shadow/shadow-engine/entity/CMakeLists.txt index 7bea09da..c7c49907 100644 --- a/projs/shadow/shadow-engine/entity/CMakeLists.txt +++ b/projs/shadow/shadow-engine/entity/CMakeLists.txt @@ -2,7 +2,12 @@ FILE(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.cpp ) +SET(TESTS + ${CMAKE_CURRENT_LIST_DIR}/tests/managers.test.cpp +) + target_shadow_module(shadow-engine SOURCES ${SOURCES} INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/inc/ + TESTS ${TESTS} ) \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/EntitySystem.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/EntitySystem.h index ba23e4ca..3f62489b 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/EntitySystem.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/EntitySystem.h @@ -2,36 +2,37 @@ #include "shadow/core/Module.h" -#include "graph/graph.h" -#include "NodeManager.h" +#include "shadow/entitiy/graph/nodes.h" #include "shadow/event-bus/events.h" +#include "shadow/entitiy/graph/managers.h" + //Holds the reference to the active scene namespace SH::Entities { - /** - * The module that manages all the entities and Scenes - */ - class API EntitySystem : public SH::Module { - SHObject_Base(EntitySystem) - private: - World world; + /** + * The module that manages all the entities and Scenes + */ + class API EntitySystem : public SH::Module { + SHObject_Base(EntitySystem) + private: + World world; - public: - EntitySystem(); + public: + EntitySystem(); - ~EntitySystem() override; + ~EntitySystem() override; - World &GetWorld() { return world; } + World &GetWorld() { return world; } - // event functions + // event functions - void Init() override; + void Init() override; - void Update(int frame) override; + void Update(int frame) override; - void OverlayRender(SH::Events::OverlayRender &); - }; + void OverlayRender(SH::Events::OverlayRender &); + }; } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeContainer.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeContainer.h index cd6e4da0..41cc237a 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeContainer.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeContainer.h @@ -11,352 +11,352 @@ namespace SH::Entities { - //TODO: this could be converted into a generic container + //TODO: this could be converted into a generic container - class INodeContainer { - public: + class INodeContainer { + public: + + virtual void *allocate() = 0; + + virtual void DestroyObject(void *object) = 0; - virtual void *allocate() = 0; + virtual std::string getTypeName() = 0; - virtual void DestroyObject(void *object) = 0; + virtual int getCount() = 0; + }; - virtual std::string getTypeName() = 0; + /** + * Node container is a memory manager for a single type of Node + * This creates s that contain a block of memory for + * ``MAX_OBJECTS_IN_CHUNK`` number of nodes, when it gets full it creates a new MemoryChunk. + * This container is created by the for each entity type that gets registered. + * @tparam Type + */ + template + class NodeContainer : public INodeContainer { - virtual int getCount() = 0; + /** + * This represents a single element of the memory chunks + * and is used for accessing the given element as either a pointer to the next free slot or as the Entity + */ + union Element { + public: + Element *next; + Type element; }; + //TODO: cosntexp + /** + * The maximum number of entities in a MemoryChunk + * This is basically the size of the memory array that gets allocated. + */ + static const size_t MAX_OBJECTS_IN_CHUNK = 2048; + + //TODO: cosntexp /** - * Node container is a memory manager for a single type of Node - * This creates s that contain a block of memory for - * ``MAX_OBJECTS_IN_CHUNK`` number of nodes, when it gets full it creates a new MemoryChunk. - * This container is created by the for each entity type that gets registered. - * @tparam Type + * The size of a single Entity */ - template - class NodeContainer : public INodeContainer { - - /** - * This represents a single element of the memory chunks - * and is used for accessing the given element as either a pointer to the next free slot or as the Entity - */ - union Element { - public: - Element *next; - Type element; - }; - - //TODO: cosntexp - /** - * The maximum number of entities in a MemoryChunk - * This is basically the size of the memory array that gets allocated. - */ - static const size_t MAX_OBJECTS_IN_CHUNK = 2048; - - //TODO: cosntexp - /** - * The size of a single Entity - */ - static const size_t ELEMENT_SIZE = (sizeof(Element)); - - //TODO: cosntexp - /** - * The size of the Memory Chunks in bytes - */ - static const size_t ALLOC_SIZE = ELEMENT_SIZE * MAX_OBJECTS_IN_CHUNK; + static const size_t ELEMENT_SIZE = (sizeof(Element)); + //TODO: cosntexp + /** + * The size of the Memory Chunks in bytes + */ + static const size_t ALLOC_SIZE = ELEMENT_SIZE * MAX_OBJECTS_IN_CHUNK; + + public: + + class MemoryChunk { + public: + Element *chunk_start; + Element *chunk_end; + + int count; + static const bool FreeFlag = true; //TODO: WTF? + static const bool InUseFlag = false; //TODO: WTF? + bool metadata[MAX_OBJECTS_IN_CHUNK]; + + //Points to the next free element in the pool + Element *nextFree; + + MemoryChunk() : count(0) { + chunk_start = (Element *) malloc(ALLOC_SIZE); + + // Might not be needed, probably for nicer debugging.... + std::memset(chunk_start, -1, ALLOC_SIZE); + + chunk_end = &chunk_start[MAX_OBJECTS_IN_CHUNK]; + + metadata[0] = FreeFlag; + + //Sets up the free linked list + for (size_t i = 1; i < MAX_OBJECTS_IN_CHUNK; i++) { + chunk_start[i - 1].next = &chunk_start[i]; + metadata[i] = FreeFlag; + } + chunk_start[MAX_OBJECTS_IN_CHUNK - 1].next = nullptr; + nextFree = chunk_start; + } + + /** + * Allocates a new instance of the stored type. + * The allocation is just a large enough memory area, + * calling the constructor on that are if not done. + * @return pointer to the new allocation, or nullptr if no free space available + */ + Type *allocate() { + if (nextFree == nullptr) + return nullptr; + count++; + auto res = nextFree; + nextFree = nextFree->next; + + int i = ((Element *) res - (Element *) chunk_start); + metadata[i] = !FreeFlag; + + return (Type *) res; + } + + /** + * Frees a place that was previously allocated by this + * @param ptr The pointer to the start of the allocation. + */ + void free(void *ptr) { + //TODO: In debug we should check if ptr is actually inside our allocation. + + count--; + auto element = ((Element *) ptr); + element->next = nextFree; + nextFree = element; + + int i = ((Element *) ptr - (Element *) chunk_start); + metadata[i] = FreeFlag; + } + + class Iterator { + MemoryChunk *chunk; + int index; public: + Iterator() : chunk(nullptr), index(0) {} - class MemoryChunk { - public: - Element *chunk_start; - Element *chunk_end; + Iterator(MemoryChunk *chunk, int pos) : chunk(chunk), index(pos) { + while (chunk->metadata[index] != InUseFlag && index < MAX_OBJECTS_IN_CHUNK) { + index++; + } + } - int count; - static const bool FreeFlag = true; //TODO: WTF? - static const bool InUseFlag = false; //TODO: WTF? - bool metadata[MAX_OBJECTS_IN_CHUNK]; + // Prefix increment + Iterator &operator++() { + //step to next element in chunk + Next(); + return *this; + } - //Points to the next free element in the pool - Element *nextFree; + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } - MemoryChunk() : count(0) { - chunk_start = (Element *) malloc(ALLOC_SIZE); + void Next() { + do { + index++; + } while (chunk->metadata[index] != InUseFlag && index < MAX_OBJECTS_IN_CHUNK); + } - // Might not be needed, probably for nicer debugging.... - std::memset(chunk_start, -1, ALLOC_SIZE); + inline Type &operator*() const { return (chunk->chunk_start[index].element); } - chunk_end = &chunk_start[MAX_OBJECTS_IN_CHUNK]; + inline Type *operator->() const { return &(chunk->chunk_start[index].element); } - metadata[0] = FreeFlag; + inline bool operator==(const Iterator &other) const { + return ((this->chunk == other.chunk) + && (this->index == other.index)); + } - //Sets up the free linked list - for (size_t i = 1; i < MAX_OBJECTS_IN_CHUNK; i++) { - chunk_start[i - 1].next = &chunk_start[i]; - metadata[i] = FreeFlag; - } - chunk_start[MAX_OBJECTS_IN_CHUNK - 1].next = nullptr; - nextFree = chunk_start; - } + inline bool operator!=(const Iterator &other) const { + return ((this->chunk != other.chunk) + || (this->index != other.index)); + } - /** - * Allocates a new instance of the stored type. - * The allocation is just a large enough memory area, - * calling the constructor on that are if not done. - * @return pointer to the new allocation, or nullptr if no free space available - */ - Type *allocate() { - if (nextFree == nullptr) - return nullptr; - count++; - auto res = nextFree; - nextFree = nextFree->next; - - int i = ((Element *) res - (Element *) chunk_start); - metadata[i] = !FreeFlag; - - return (Type *) res; - } + int GetIndex() const { return index; } - /** - * Frees a place that was previously allocated by this - * @param ptr The pointer to the start of the allocation. - */ - void free(void *ptr) { - //TODO: In debug we should check if ptr is actually inside our allocation. + MemoryChunk *GetChunk() const { return chunk; } + }; - count--; - auto element = ((Element *) ptr); - element->next = nextFree; - nextFree = element; + inline Iterator begin() { + return Iterator(this, 0); + } - int i = ((Element *) ptr - (Element *) chunk_start); - metadata[i] = FreeFlag; - } + inline Iterator end() { + return Iterator(this, MAX_OBJECTS_IN_CHUNK); + } - class Iterator { - MemoryChunk *chunk; - int index; - public: - Iterator() : chunk(nullptr), index(0) {} - - Iterator(MemoryChunk *chunk, int pos) : chunk(chunk), index(pos) { - while (chunk->metadata[index] != InUseFlag && index < MAX_OBJECTS_IN_CHUNK) { - index++; - } - } - - // Prefix increment - Iterator &operator++() { - //step to next element in chunk - Next(); - return *this; - } - - Iterator operator++(int) { - Iterator tmp = *this; - ++(*this); - return tmp; - } - - void Next() { - do { - index++; - } while (chunk->metadata[index] != InUseFlag && index < MAX_OBJECTS_IN_CHUNK); - } - - inline Type &operator*() const { return (chunk->chunk_start[index].element); } - - inline Type *operator->() const { return &(chunk->chunk_start[index].element); } - - inline bool operator==(const Iterator &other) const { - return ((this->chunk == other.chunk) - && (this->index == other.index)); - } - - inline bool operator!=(const Iterator &other) const { - return ((this->chunk != other.chunk) - || (this->index != other.index)); - } - - int GetIndex() const { return index; } - - MemoryChunk *GetChunk() const { return chunk; } - }; - - inline Iterator begin() { - return Iterator(this, 0); - } + }; - inline Iterator end() { - return Iterator(this, MAX_OBJECTS_IN_CHUNK); - } + using MemoryChunks = std::vector; - }; + class Iterator { + NodeContainer *container; + int chunk_index; - using MemoryChunks = std::vector; + typename MemoryChunk::Iterator element; + public: + Iterator(NodeContainer *cont, int c_index) : + container(cont), + chunk_index(c_index) { - class Iterator { - NodeContainer *container; - int chunk_index; + if (chunk_index < container->m_chunks.size()) { + element = container->m_chunks[chunk_index]->p_start(); + } - typename MemoryChunk::Iterator element; - public: - Iterator(NodeContainer *cont, int c_index) : - container(cont), - chunk_index(c_index) { + SeekNextValid(); + } - if (chunk_index < container->m_chunks.size()) { - element = container->m_chunks[chunk_index]->begin(); - } + // Prefix increment + Iterator &operator++() { + //step to next element in chunk + element++; + SeekNextValid(); - SeekNextValid(); - } + return *this; + } - // Prefix increment - Iterator &operator++() { - //step to next element in chunk - element++; - SeekNextValid(); + void SeekNextValid() { + while (!IsEndChunk() && !IsValid()) { + Step(); + } + } - return *this; - } + [[nodiscard]] inline bool IsEndChunk() const { + return (chunk_index >= container->m_chunks.size()); + } - void SeekNextValid() { - while (!IsEndChunk() && !IsValid()) { - Step(); - } - } + [[nodiscard]] inline bool IsValid() const { + return !(element == container->m_chunks[chunk_index]->p_end()); + } - [[nodiscard]] inline bool IsEndChunk() const { - return (chunk_index >= container->m_chunks.size()); - } + void Step() { + chunk_index++; + if (IsEndChunk()) { + element = typename MemoryChunk::Iterator(); + } else { + element = container->m_chunks[chunk_index]->p_start(); + } + } - [[nodiscard]] inline bool IsValid() const { - return !(element == container->m_chunks[chunk_index]->end()); - } + // Postfix increment + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } - void Step() { - chunk_index++; - if (IsEndChunk()) { - element = typename MemoryChunk::Iterator(); - } else { - element = container->m_chunks[chunk_index]->begin(); - } - } + inline Type &operator*() const { return (*element); } - // Postfix increment - Iterator operator++(int) { - Iterator tmp = *this; - ++(*this); - return tmp; - } + inline Type *operator->() const { return &(*element); } - inline Type &operator*() const { return (*element); } + inline bool operator==(const Iterator &other) const { + return ((this->container == other.container) + && (this->chunk_index == other.chunk_index) + && (this->element == other.element)); + } - inline Type *operator->() const { return &(*element); } + inline bool operator!=(const Iterator &other) const { + return ((this->container != other.container) + || (this->chunk_index != other.chunk_index) + || (this->element != other.element)); + } - inline bool operator==(const Iterator &other) const { - return ((this->container == other.container) - && (this->chunk_index == other.chunk_index) - && (this->element == other.element)); - } + NodeContainer *GetContainer() const { return container; } - inline bool operator!=(const Iterator &other) const { - return ((this->container != other.container) - || (this->chunk_index != other.chunk_index) - || (this->element != other.element)); - } + int GetChunkIndex() const { return chunk_index; } - NodeContainer *GetContainer() const { return container; } + typename MemoryChunk::Iterator GetElement() const { return element; } - int GetChunkIndex() const { return chunk_index; } + }; - typename MemoryChunk::Iterator GetElement() const { return element; } + MemoryChunks m_chunks; - }; + public: - MemoryChunks m_chunks; + NodeContainer() { + m_chunks.clear(); + } - public: + void *allocate() override { + void *slot = nullptr; - NodeContainer() { - m_chunks.clear(); - } + // get next free slot + for (auto chunk : this->m_chunks) { + if (chunk->count > MAX_OBJECTS_IN_CHUNK) + continue; - void *allocate() { - void *slot = nullptr; - - // get next free slot - for (auto chunk : this->m_chunks) { - if (chunk->count > MAX_OBJECTS_IN_CHUNK) - continue; - - slot = chunk->allocate(); - if (slot != nullptr) { - //chunk->objects.push_back((OBJECT_TYPE*)slot); - break; - } - //TODO: if we got here that is impossible... - // If ``chunk->count > MAX_OBJECTS_IN_CHUNK`` was right but we still got nullptr - // than we got a misalignment + slot = chunk->allocate(); + if (slot != nullptr) { + //chunk->objects.push_back((OBJECT_TYPE*)slot); + break; } + //TODO: if we got here that is impossible... + // If ``chunk->count > MAX_OBJECTS_IN_CHUNK`` was right but we still got nullptr + // than we got a misalignment + } - // all chunks are full... allocate a new one - if (slot == nullptr) { - //Allocator* allocator = new Allocator(ALLOC_SIZE, allocate(ALLOC_SIZE, this->m_AllocatorTag), sizeof(OBJECT_TYPE), alignof(OBJECT_TYPE)); - MemoryChunk *newChunk = new MemoryChunk(); - - // put new chunk in front - this->m_chunks.push_back(newChunk); + // all chunks are full... allocate a new one + if (slot == nullptr) { + //Allocator* allocator = new Allocator(ALLOC_SIZE, allocate(ALLOC_SIZE, this->m_AllocatorTag), sizeof(OBJECT_TYPE), alignof(OBJECT_TYPE)); + MemoryChunk *newChunk = new MemoryChunk(); - slot = newChunk->allocate(); + // put new chunk in front + this->m_chunks.push_back(newChunk); - assert(slot != nullptr && "Unable to create new object. Out of memory?!"); - //newChunk->objects.clear(); - //newChunk->objects.push_back((OBJECT_TYPE*)slot); - } + slot = newChunk->allocate(); - return slot; + assert(slot != nullptr && "Unable to create new object. Out of memory?!"); + //newChunk->objects.clear(); + //newChunk->objects.push_back((OBJECT_TYPE*)slot); } - Type *allocateWithType() { - return (Type *) allocate(); - } + return slot; + } - void DestroyObject(void *object) { - intptr_t adr = reinterpret_cast(object); + Type *allocateWithType() { + return (Type *) allocate(); + } - for (auto chunk : this->m_chunks) { - if (((intptr_t) chunk->chunk_start) <= adr && adr < (intptr_t) chunk->chunk_end) { - // note: no need to call d'tor since it was called already by 'delete' + void DestroyObject(void *object) override { + intptr_t adr = reinterpret_cast(object); - //chunk->objects.remove((OBJECT_TYPE*)object); - chunk->free(object); - return; - } - } + for (auto chunk : this->m_chunks) { + if (((intptr_t) chunk->chunk_start) <= adr && adr < (intptr_t) chunk->chunk_end) { + // note: no need to call d'tor since it was called already by 'delete' - assert(false && "Failed to delete object. Memory corruption?!"); + //chunk->objects.remove((OBJECT_TYPE*)object); + chunk->free(object); + return; + } } - std::string getTypeName() override { - return Type::Type(); - } + assert(false && "Failed to delete object. Memory corruption?!"); + } - int getCount() override { - int count = 0; + std::string getTypeName() override { + return Type::Type(); + } - for (auto chunk : this->m_chunks) { - count += chunk->count; - } + int getCount() override { + int count = 0; - return count; + for (auto chunk : this->m_chunks) { + count += chunk->count; } - inline Iterator begin() { return Iterator(this, 0); } + return count; + } - inline Iterator end() { return Iterator(this, m_chunks.size()); } + inline Iterator begin() { return Iterator(this, 0); } - }; + inline Iterator end() { return Iterator(this, m_chunks.size()); } + + }; } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeManager.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeManager.h deleted file mode 100644 index 5efb53e8..00000000 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/NodeManager.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "shadow/entitiy/NodeContainer.h" -#include "shadow/SHObject.h" -#include "shadow/entitiy/graph/graph.h" - -namespace SH::Entities { - - class NodeManager; - -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/SystemManager.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/SystemManager.h index 46741465..170cbc3f 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/SystemManager.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/SystemManager.h @@ -1,7 +1,7 @@ #pragma once #include -#include "graph/graph.h" +#include "graph/nodes.h" #include "entities/Position.h" namespace SH::Entities { diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Light.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Light.h index 3d855778..b9fce9af 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Light.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Light.h @@ -2,27 +2,27 @@ #include -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" //A light component in the builtin namespace with light color, type, and intensity namespace SH::Entities::Builtin { - //enum of light types - enum class LightType { - Directional, - Point, - Spot - }; + //enum of light types + enum class LightType { + Directional, + Point, + Spot + }; - class API Light : public SH::Entities::Component { - SHObject_Base(Light) - public: - Light() : Component() {} + class API Light : public SH::Entities::Component { + SHObject_Base(Light) + public: + Light() : Component() {} - private: - glm::vec3 color; - float intensity; - LightType type; - }; + private: + glm::vec3 color; + float intensity; + LightType type; + }; } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/MeshComponent.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/MeshComponent.h index 75b4785b..6a094317 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/MeshComponent.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/MeshComponent.h @@ -1,19 +1,19 @@ #pragma once -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" #include "shadow/assets/Mesh.h" namespace SH::Entities::Builtin { - //A component that holds a mesh reference - class API MeshComponent : public SH::Entities::Component { - SHObject_Base(MeshComponent) - public: - MeshComponent() : Component() {} + //A component that holds a mesh reference + class API MeshComponent : public SH::Entities::Component { + SHObject_Base(MeshComponent) + public: + MeshComponent() : Component() {} - private: - std::shared_ptr mesh; - }; + private: + std::shared_ptr mesh; + }; } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/NullActor.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/NullActor.h index 83e93239..a3e52eb5 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/NullActor.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/NullActor.h @@ -1,16 +1,16 @@ #pragma once -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" namespace SH::Entities::Builtin { - //Basic NullActor inherited from Actor - class API NullActor : public Actor { - SHObject_Base(NullActor) + //Basic NullActor inherited from Actor + class API NullActor : public Actor { + SHObject_Base(NullActor) - public: - //Empty Build function - void Build() override {}; - }; + public: + //Empty Build function + void Build() override {}; + }; } diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Position.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Position.h index 8b6f75a1..34e9b05d 100644 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Position.h +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/entities/Position.h @@ -1,20 +1,20 @@ #pragma once -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" //Position component in the builtin namespace namespace SH::Entities::Builtin { - class API Position : public SH::Entities::Component { - SHObject_Base(Position) - public: - float x = 0; - float y = 0; - float z = 0; + class API Position : public SH::Entities::Component { + SHObject_Base(Position) + public: + float x = 0; + float y = 0; + float z = 0; - Position() = default; + Position() = default; - Position(float x_, float y_, float z_) - : x(x_), y(y_), z(z_) {} - }; + Position(float x_, float y_, float z_) + : x(x_), y(y_), z(z_) {} + }; } diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/NodeFunctions.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/NodeFunctions.h new file mode 100644 index 00000000..abe1fd4b --- /dev/null +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/NodeFunctions.h @@ -0,0 +1,8 @@ +#pragma once + +#include "shadow/entitiy/graph/nodes.h" + +namespace SH::Entities { + + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/archetype.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/archetype.h new file mode 100644 index 00000000..29c58570 --- /dev/null +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/archetype.h @@ -0,0 +1,36 @@ + +#include +#include +#include +#include + +#include "shadow/SHObject.h" +#include "shadow/util/hash.h" +#include "shadow/exports.h" +#include "shadow/entitiy/graph/nodes.h" + + + + + + + + +/* + * + * | id: 0 | row: 0 | | ID | Child1 | + * ------- | id: 0 | row: 0 | |id: 0 | Oxa23 | + * | id | ---> | id: 0 | row: 0 | |id: 1 | Ox... | + * |uuid | | id: 0 | row: 0 | |id: 2 | Ox... + * ------- | id: 0 | row: 0 | |id: 3 | + * | id: 0 | row: 0 | |id: 4 | + * | id: 0 | row: 0 | |id: 5 | + * | id: 0 | row: 0 | | + * + * + * + * + */ + + + diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/graph.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/graph.h deleted file mode 100644 index bc4427b8..00000000 --- a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/graph.h +++ /dev/null @@ -1,508 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "shadow/SHObject.h" -#include "shadow/entitiy/NodeManager.h" - -namespace SH::Entities { - - typedef int RtmUuid; - - constexpr RtmUuid INVALID_UID = -1; - - namespace Debugger { - class AllocationDebugger; - } - class NodeBase; - - class Node; - - class Actor; - - class Scene; - - class World; - - /** - * Runtime pointer to an Entity - * It tracks the UUID of the linked entity - * @tparam Type - */ - template - class rtm_ptr { - private: - Type *m_ptr; - - RtmUuid m_uid; - - public: - rtm_ptr(Type *ptr) : m_ptr(ptr), m_uid(ptr->m_runtime_uid) {} - - rtm_ptr() : m_ptr(nullptr) {} - - template - explicit rtm_ptr(const rtm_ptr &o) { - m_ptr = (Type *) o.GetInternalPointer(); - m_uid = o.GetInternalUid(); - } - - Type *operator->() const { - if (m_ptr->m_runtime_uid != m_uid) { - assert(m_ptr->m_runtime_uid == m_uid); - return nullptr; - } - return ((Type *) m_ptr); - } - - bool IsValid() const { return m_ptr != nullptr && m_ptr->m_runtime_uid == m_uid; } - - inline operator bool() const { return this->IsValid(); } - - template - inline bool operator==(rtm_ptr o) const { - return m_ptr == o.m_ptr && - m_uid == o.m_uid; - } - - template - inline operator rtm_ptr() const { - return rtm_ptr(m_ptr); - } - - void SetNull() { - m_ptr = nullptr; - m_uid = -1; - } - - void *GetInternalPointer() const { - return (void *) m_ptr; - } - - NodeBase *GetAsNodeBase() const { - return (NodeBase *) m_ptr; - } - - Type *Get() const { - return m_ptr; - } - - int GetInternalUid() const { return m_uid; } - - }; - - /** - * The base class for all things in the scene graph - */ - class API NodeBase : public SHObject { - SHObject_Base(NodeBase) - - /** - * - * This is the Globally unique ID of this Entity - * - * This ID will be only assigned to this Entity instance - * It can be used to look up entities, but it is not recommended as it is a slow process - * For Entity Lookup use the m_runtime_index - */ - RtmUuid m_runtime_uid; - - /** - * @brief The index of this entity in the Entity Look Up Table - * This is a fast way to access the entity, but it is not unique - * If an Entity is freed up it's index will be given out to another Entity of the same type - */ - int m_runtime_index; - - protected: - NodeBase() {}; - - rtm_ptr parent; - rtm_ptr m_scene; - World *m_world; - public: - template friend - class rtm_ptr; - - friend class NodeManager; - - virtual ~NodeBase() {}; - - void SetParent(const rtm_ptr &parent); - - rtm_ptr GetParent() const { return parent; } - - void SetScene(const rtm_ptr &scene); - - void SetWorld(World *world); - - void Destroy(); - }; - -//########################################################### -//#################### Leaf nodes ########################### -//########################################################### - - // TODO: I don't think there can be any other types of leaf nodes than components, this is only here to make the - // inheritance names better - class API LeafNode : public NodeBase { - SHObject_Base(LeafNode) - }; - - class API Component : public LeafNode { - SHObject_Base(Component) - }; - - -//########################################################### -//#################### Complex nodes ######################## -//########################################################### - - // TODO: same with these the only difference is that Actors have a name as well. - // These two can be merged together if no use is found for unnamed full nodes - - template - concept IsActor = std::derived_from; - - class API Node : public NodeBase { - SHObject_Base(Node) - std::vector> hierarchy; - std::vector> internal_hierarchy; - - public: - std::vector> &GetHierarchy() { return hierarchy; } - - void AddChild(const rtm_ptr &child, bool internal = false); - - template - requires (not IsActor) - rtm_ptr Add(const T &node, bool internal = false); - - template - requires (IsActor) - rtm_ptr Add(const T &node, bool internal = false); - - void RemoveChild(const rtm_ptr &child, bool internal = false); - }; - - class API Actor : public Node { - SHObject_Base(Actor) - protected: - /** - * The name of this actor - */ - std::string name; - - public: - virtual void Build() = 0; - - const std::string& GetName() const { return name; } - - void SetName(std::string name) { this->name = name; } - }; - - class API Entity : public Node { - SHObject_Base(Entity) - }; - - class API Scene : public Actor { - SHObject_Base(Scene) - std::vector> static_hierarchy; - public: - Scene(std::string name) : Actor() { this->name = name; } - - void Build() override {}; - - std::vector> &GetStaticHierarchy() { return static_hierarchy; } - }; - - /** - * It is responsible for the allocation of nodes and does not care about the graph of them - * @brief Manages the memory and IDs of entities - */ - class API NodeManager { - //Map the runtime index of the entity to the container - using NodeContainerRegistry = std::unordered_map; - - /** - * @brief Map of the Entity Containers mapped to the entity type ID - */ - NodeContainerRegistry m_NodeContainerRegistry; - - using NodeLookupTable = std::vector; - - /** - * This table is used to get a Entity by it's runtime Index. - * It is a fast lookup, but it is not unique. - * If a Entity is freed up it's index will be given out to another Entity of the same type - * - * To use the table simply access the element at the index of the Entity. - * - * @brief Quick access Look Up Table of active entities - * - */ - NodeLookupTable m_NodeLUT; - - //Extra number of spaces to allocate in the LUT - const int NODE_LUT_GROW = 2048; - int LUTNextFree = 0; - bool LUTFragm = false; - std::vector LUTFragmFree; - - /** - * @brief The next assignable Unique ID - */ - int nextUID = 0; - - /** - * @brief Returns the correct container for the entity type. - * Does not create a new one if it does not exist - * @param typeID The type ID of the entity - * @return The entity container accosted with this type - */ - INodeContainer *GetNodeContainer(int typeID); - - /** - * @brief Returns the correct container for the entity type, - * creating a new one if it does not exist - * @tparam T The type of the entity - * @return The entity container accosted with this type - */ - template - requires std::is_base_of::value - inline NodeContainer *GetNodeContainer() { - int CID = T::TypeId(); - - auto container = (NodeContainer *) GetNodeContainer(CID); - - if (container == nullptr) { - container = new NodeContainer(); - m_NodeContainerRegistry[CID] = container; - } - - return container; - } - - /** - * @brief Assigns the next free LUT index to this entity - * @param component - * @return - */ - int AssignIndexToNode(NodeBase *component); - - /** - * @brief Frees up the given index - * @param id - */ - void ReleaseIndex(int id); - - template - rtm_ptr TakeNode(const T &node) { - // acquire memory for new entity object of type Type - void *pObjectMemory = GetNodeContainer()->allocate(); - - new(pObjectMemory)T(node); - - //Assign the index and the UID to the object - int runtimeIndex = this->AssignIndexToNode((T *) pObjectMemory); - ((T *) pObjectMemory)->m_runtime_index = runtimeIndex; - ((T *) pObjectMemory)->m_runtime_uid = nextUID; - nextUID++; - - return rtm_ptr((T *) pObjectMemory); - } - - public: - friend class SH::Entities::Debugger::AllocationDebugger; - - NodeManager(); - - template - rtm_ptr Add(const T &node) { - rtm_ptr ptr = TakeNode(node); - return ptr; - } - - /** - * @brief Instantiates a new entity - * @tparam T Type of the Entity - * @tparam ARGS Constructor parameters of the Entity - * @param args Constructor parameters of the Entity - * @return - * @obsolete - */ - template - rtm_ptr ConstructNode(ARGS &&... args) { - //The type ID of the Entity we are trying to add - const int CTID = T::TypeId(); - - // acquire memory for new entity object of type Type - void *pObjectMemory = GetNodeContainer()->allocate(); - - // create Entity in place - NodeBase *component = new(pObjectMemory)T(std::forward(args)...); - - //Assign the index and the UID to the object - int runtimeIndex = this->AssignIndexToNode((T *) pObjectMemory); - ((T *) pObjectMemory)->m_runtime_index = runtimeIndex; - ((T *) pObjectMemory)->m_runtime_uid = nextUID; - nextUID++; - - return rtm_ptr((T *) component); - } - - void DestroyNode(int node_index, int typeID); - - template - requires std::is_base_of::value - void DestroyNode(T *node) { - DestroyNode(node->m_runtime_index, node->GetTypeId()); - } - - template - void DestroyNode(rtm_ptr node) { - DestroyNode(node->m_runtime_index, node->GetTypeId()); - } - - template - inline T *GetEntityByIndex(int index) { - return this->m_NodeLUT[index]; - } - - template - inline NodeContainer *GetContainerByType() { - int CID = T::TypeId(); - - auto it = this->m_NodeContainerRegistry.find(CID); - - if (it == this->m_NodeContainerRegistry.end()) - return nullptr; - - return static_cast *>(it->second); - } - }; - - class API SystemBase { - public: - //SystemBase() {} - //virtual ~SystemBase() = default; - - virtual void run(NodeManager &nmgr) = 0; - }; - - template requires std::derived_from - class API System : public SystemBase { - std::function m_Func; - - public: - System &forEach(std::function func) { - this->m_Func = func; - return *this; - } - - void run(NodeManager &nmgr) override { - auto &container = *nmgr.GetContainerByType(); - - for (auto it = container.begin(), end = container.end(); it != end; ++it) { - m_Func(*it); - } - } - }; - - class API SystemManager { - NodeManager &nodeManager; - - //vector storing the systems - std::vector> m_Systems; - - public: - SystemManager(NodeManager &nmgr) : nodeManager(nmgr) { - - } - - template - std::shared_ptr> system() { - auto ptr = std::make_unique>(); - m_Systems.push_back(std::make_shared>()); - return std::dynamic_pointer_cast>(m_Systems.back()); - } - - void run() { - for (auto &s : m_Systems) { - s->run(nodeManager); - } - } - }; - - class API RootNode : public Node { - SHObject_Base(RootNode) - }; - - class API World : SHObject { - SHObject_Base(World) - rtm_ptr root; - - NodeManager manager; - - SystemManager systemManager; - public: - World(); - - template - requires std::derived_from - rtm_ptr Add(const T &node) { - auto ptr = manager.Add(node); - ptr->SetWorld(this); - return ptr; - } - - template - requires std::derived_from - rtm_ptr AddScene(const T &scene) { - return root->Add(scene); - } - - template - std::shared_ptr> system() { - return systemManager.system(); - } - - void Step() { - systemManager.run(); - } - - NodeManager &GetManager() { return manager; } - - rtm_ptr GetRoot() { return root; } - - void Destroy(NodeBase *node); - }; - - template - requires (not IsActor) - rtm_ptr Node::Add(const T &node, bool internal) { - auto ptr = m_world->Add(node); - - this->AddChild(ptr, internal); - - return ptr; - } - - template - requires (IsActor) - rtm_ptr Node::Add(const T &node, bool internal) { - auto ptr = m_world->Add(node); - - this->AddChild(ptr, internal); - - if (Actor *h_actor = dynamic_cast(ptr.Get())) - ptr->Build(); - - return ptr; - } - -} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/managers.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/managers.h new file mode 100644 index 00000000..8e221159 --- /dev/null +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/managers.h @@ -0,0 +1,256 @@ +#pragma once +#include "shadow/exports.h" +#include "shadow/SHObject.h" +#include "shadow/util/hash.h" + +#include "shadow/entitiy/graph/nodes.h" +#include "shadow/entitiy/NodeContainer.h" + +namespace SH::Entities { + + using ComponentTypeId = SH::TypeId; + using EntityId = RtmUuid; + + // List of component types + using Types = std::vector; +} + +template<> +struct std::hash { +std::size_t operator()(const SH::Entities::Types &s) const noexcept; +}; + + +namespace SH::Entities{ + + class API NodeManager{ + + //############################################## + //############# Storage ###################### + //############################################## + + //Map the runtime index of the entity to the container + using NodeContainerRegistry = std::unordered_map; + + NodeContainerRegistry m_NodeContainerRegistry; + + //############################################## + //############# Archetypes ################### + //############################################## + + /** + * @brief The next assignable Unique ID + */ + int nextUID = 0; + + struct Archetype { + // Unique id of an archetype + using Id = std::uint32_t; + // vector for a component type T + using Column = std::vector; + + Id id; + Types types; + std::vector children; + }; + Archetype::Id next_archetype_id = 0; + + std::vector archetype; + + struct ArchetypeRecord { + size_t column; + }; + using ArchetypeMap = std::unordered_map; + ///Maps each component type to every archetype it is part of + std::unordered_map component_index; + + struct Record { + public: + Archetype *archetype; + size_t row; + }; + ///Maps each entity to the archetype and index in the archetype + std::unordered_map entity_index; + + std::unordered_map archetype_index; + + void *get_component(const EntityId &entity, const ComponentTypeId &component); + + template + void* get_component(const EntityId& entity); + + void add_component(const EntityId& entity, const NodeBase& child); + + const Archetype& get_archetype(const Types types); + + /** + * @brief Returns the correct container for the entity type. + * Does not create a new one if it does not exist + * @param typeID The type ID of the entity + * @return The entity container accosted with this type + */ + INodeContainer *GetNodeContainer(int typeID); + + /** + * @brief Returns the correct container for the entity type, + * creating a new one if it does not exist + * @tparam T The type of the entity + * @return The entity container accosted with this type + */ + template + inline NodeContainer *GetNodeContainer() { + int CID = T::TypeId(); + + auto container = (NodeContainer *) GetNodeContainer(CID); + + if (container == nullptr) { + container = new NodeContainer(); + m_NodeContainerRegistry[CID] = container; + } + + return container; + } + + /** + * @brief Assigns the next free LUT index to this entity + * @param component + * @return + */ + int AssignIndexToNode(NodeBase *component); + + template + rtm_ptr TakeNode(const T &node) { + // acquire memory for new entity object of type Type + void *pObjectMemory = GetNodeContainer()->allocate(); + + new(pObjectMemory)T(node); + + //Assign the index and the UID to the object + int runtimeIndex = this->AssignIndexToNode((T *) pObjectMemory); + ((T *) pObjectMemory)->m_runtime_index = runtimeIndex; + ((T *) pObjectMemory)->m_runtime_uid = nextUID; + nextUID++; + + return rtm_ptr((T *) pObjectMemory); + } + + + public: + void AddNode(const NodeBase &node, const EntityId parent = INVALID_UID ){ + auto newNode = this->TakeNode(node); + if(parent != INVALID_UID){ + add_component(parent, node); + } + } + }; + + + class API SystemBase { + public: + virtual void run(NodeManager &nmgr) = 0; + }; + + template + class API System : public SystemBase { + std::function m_Func; + + public: + System &forEach(std::function func) { + this->m_Func = func; + return *this; + } + + void run(NodeManager &nmgr) override { + /* + auto &container = *nmgr.GetContainerByType(); + + for ( + auto it = container.begin(), end = container.end(); + it != + end; + ++it) { + m_Func(*it); + } + */ + } + }; + + class API SystemManager { + NodeManager &nodeManager; + + //vector storing the systems + std::vector> m_Systems; + + public: + SystemManager(NodeManager &nmgr) : nodeManager(nmgr) { + + } + + template + std::shared_ptr> system() { + auto ptr = std::make_unique>(); + m_Systems.push_back(std::make_shared>()); + return std::dynamic_pointer_cast>(m_Systems.back()); + } + + void run() { + for (auto &s : m_Systems) { + s->run(nodeManager); + } + } + }; + + class API RootNode : public Node { + SHObject_Base(RootNode) + }; + + class API World : SHObject { + SHObject_Base(World) + rtm_ptr root; + + NodeManager manager; + + SystemManager systemManager; + public: + World(); + + template + rtm_ptr Add(const T &node) { + //auto ptr = manager.Add(node); + //ptr->SetWorld(this); + //return ptr; + } + + template + requires std::derived_from + rtm_ptr AddScene(const T &scene) { + return root->Add(scene); + } + + template + std::shared_ptr> system() { + return systemManager.system(); + } + + void Step() { + systemManager.run(); + } + + NodeManager &GetManager() { return manager; } + + rtm_ptr GetRoot() { return root; } + + void Destroy(NodeBase *node); + }; + + + template + rtm_ptr Node::Add(const T &node, bool internal) { + auto ptr = m_world->Add(node); + + this->AddChild(ptr, internal); + + return ptr; + } + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/nodes.h b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/nodes.h new file mode 100644 index 00000000..089119a3 --- /dev/null +++ b/projs/shadow/shadow-engine/entity/inc/shadow/entitiy/graph/nodes.h @@ -0,0 +1,215 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "shadow/SHObject.h" + +//#include "archetype.h" + +namespace SH::Entities { + + typedef int RtmUuid; + + constexpr RtmUuid INVALID_UID = -1; + + namespace Debugger { + class AllocationDebugger; + } + class NodeBase; + + class Node; + + class Actor; + + class Scene; + + class World; + + /** + * Runtime pointer to an Entity + * It tracks the UUID of the linked entity + * @tparam Type + */ + template + class rtm_ptr { + private: + Type *m_ptr; + + RtmUuid m_uid; + + public: + rtm_ptr(Type *ptr) : m_ptr(ptr), m_uid(ptr->m_runtime_uid) {} + + rtm_ptr() : m_ptr(nullptr) {} + + template + explicit rtm_ptr(const rtm_ptr &o) { + m_ptr = (Type *) o.GetInternalPointer(); + m_uid = o.GetInternalUid(); + } + + Type *operator->() const { + if (m_ptr->m_runtime_uid != m_uid) { + assert(m_ptr->m_runtime_uid == m_uid); + return nullptr; + } + return ((Type *) m_ptr); + } + + bool IsValid() const { return m_ptr != nullptr && m_ptr->m_runtime_uid == m_uid; } + + inline operator bool() const { return this->IsValid(); } + + template + inline bool operator==(rtm_ptr o) const { + return m_ptr == o.m_ptr && + m_uid == o.m_uid; + } + + template + inline operator rtm_ptr() const { + return rtm_ptr(m_ptr); + } + + void SetNull() { + m_ptr = nullptr; + m_uid = -1; + } + + void *GetInternalPointer() const { + return (void *) m_ptr; + } + + NodeBase *GetAsNodeBase() const { + return (NodeBase *) m_ptr; + } + + Type *Get() const { + return m_ptr; + } + + int GetInternalUid() const { return m_uid; } + + }; + + /** + * The base class for all things in the scene graph + */ + class API NodeBase : public SHObject { + SHObject_Base(NodeBase) + + /** + * + * This is the Globally unique ID of this Entity + * + * This ID will be only assigned to this Entity instance + * It can be used to look up entities, but it is not recommended as it is a slow process + * For Entity Lookup use the m_runtime_index + */ + RtmUuid m_runtime_uid; + + /** + * @brief The index of this entity in the Entity Look Up Table + * This is a fast way to access the entity, but it is not unique + * If an Entity is freed up it's index will be given out to another Entity of the same type + */ + int m_runtime_index; + + protected: + NodeBase() {}; + + rtm_ptr parent; + rtm_ptr m_scene; + World *m_world; + public: + template friend + class rtm_ptr; + + friend class NodeManager; + + virtual ~NodeBase() {}; + + void SetParent(const rtm_ptr &parent); + + rtm_ptr GetParent() const { return parent; } + + void SetScene(const rtm_ptr &scene); + + void SetWorld(World *world); + + void Destroy(); + }; + + template + concept NodeBaseType = std::is_base_of::value; + +//########################################################### +//#################### Leaf nodes ########################### +//########################################################### + + class API Component : public NodeBase { + SHObject_Base(Component) + }; + +//########################################################### +//#################### Complex nodes ######################## +//########################################################### + + // TODO: the only difference is that Actors have a name as well. + // These two can be merged together if no use is found for unnamed full nodes + + template + concept IsActor = std::derived_from; + + class API Node : public NodeBase { + SHObject_Base(Node) + std::vector> hierarchy; + std::vector> internal_hierarchy; + + public: + template + rtm_ptr Add(const T &node, bool internal = false); + + std::vector> &GetHierarchy() { return hierarchy; } + + void AddChild(const rtm_ptr &child, bool internal = false); + + void RemoveChild(const rtm_ptr &child, bool internal = false); + }; + + class API Actor : public Node { + SHObject_Base(Actor) + protected: + /** + * The name of this actor + */ + std::string name; + + public: + virtual void Build() = 0; + + const std::string &GetName() const { return name; } + + void SetName(std::string name) { this->name = name; } + }; + + class API Entity : public Node { + SHObject_Base(Entity) + }; + + class API Scene : public Actor { + SHObject_Base(Scene) + std::vector> static_hierarchy; + public: + Scene(std::string name) : Actor() { this->name = name; } + + void Build() override {}; + + std::vector> &GetStaticHierarchy() { return static_hierarchy; } + }; + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/src/NodeManager.cpp b/projs/shadow/shadow-engine/entity/src/NodeManager.cpp index 78ef9381..b55ca350 100644 --- a/projs/shadow/shadow-engine/entity/src/NodeManager.cpp +++ b/projs/shadow/shadow-engine/entity/src/NodeManager.cpp @@ -1,72 +1,73 @@ - -#include "shadow/entitiy/NodeManager.h" -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/managers.h" namespace SH::Entities { - //NodeManager *NodeManager::Instance = nullptr; - - int NodeManager::AssignIndexToNode(NodeBase *component) { - int i = 0; - if (LUTFragm) { - i = LUTFragmFree.back(); - LUTFragmFree.pop_back(); - if (LUTFragmFree.empty()) { - LUTFragm = false; - } - } else { - i = LUTNextFree; - LUTNextFree++; - if (!(i < m_NodeLUT.size())) { - this->m_NodeLUT.resize(this->m_NodeLUT.size() + NODE_LUT_GROW, nullptr); - } - } - - this->m_NodeLUT[i] = component; - return i; - } - - void NodeManager::ReleaseIndex(int id) { - assert(id < this->m_NodeLUT.size() && "Invalid component id"); - - //If this free is from the middle of the LUT - //We record that the LUT is fragmented - if (id != this->m_NodeLUT.size() - 1) { - LUTFragm = true; - LUTFragmFree.push_back(id); - } - - this->m_NodeLUT[id] = nullptr; - } - - void NodeManager::DestroyNode(const int node_index, const int typeID) { - //Lookup of the entity to be removed - NodeBase *entity = this->m_NodeLUT[node_index]; - assert(entity != nullptr && "FATAL: Trying to remove a entity that doesn't exist"); - - //Invalidate the UID - entity->m_runtime_uid = INVALID_UID; - // unmap entity id - ReleaseIndex(node_index); - - entity->~NodeBase(); - - // release object memory - GetNodeContainer(typeID)->DestroyObject((void *) entity); - } - - INodeContainer *NodeManager::GetNodeContainer(int typeID) { - auto it = this->m_NodeContainerRegistry.find(typeID); - INodeContainer *cc = nullptr; - - if (!(it == this->m_NodeContainerRegistry.end())) - cc = static_cast(it->second); - - return cc; - } - - NodeManager::NodeManager() { - //Instance = this; - } + int NodeManager::AssignIndexToNode(NodeBase *component) { + /* + int i = 0; + if (LUTFragm) { + i = LUTFragmFree.back(); + LUTFragmFree.pop_back(); + if (LUTFragmFree.empty()) { + LUTFragm = false; + } + } else { + i = LUTNextFree; + LUTNextFree++; + if (!(i < m_NodeLUT.size())) { + this->m_NodeLUT.resize(this->m_NodeLUT.size() + NODE_LUT_GROW, nullptr); + } + } + + this->m_NodeLUT[i] = component; + */ + return 1; + } + + /* + void NodeManager::ReleaseIndex(int id) { + + assert(id < this->m_NodeLUT.size() && "Invalid component id"); + + //If this free is from the middle of the LUT + //We record that the LUT is fragmented + if (id != this->m_NodeLUT.size() - 1) { + LUTFragm = true; + LUTFragmFree.push_back(id); + } + + this->m_NodeLUT[id] = nullptr; + + } + */ + + /* + void NodeManager::DestroyNode(const int node_index, const int typeID) { + //Lookup of the entity to be removed + NodeBase *entity = this->m_NodeLUT[node_index]; + assert(entity != nullptr && "FATAL: Trying to remove a entity that doesn't exist"); + + //Invalidate the UID + entity->m_runtime_uid = INVALID_UID; + // unmap entity id + ReleaseIndex(node_index); + + entity->~NodeBase(); + + // release object memory + GetNodeContainer(typeID)->DestroyObject((void *) entity); + } + */ + + INodeContainer *NodeManager::GetNodeContainer(int typeID) { + auto it = this->m_NodeContainerRegistry.find(typeID); + INodeContainer *cc = nullptr; + + if (!(it == this->m_NodeContainerRegistry.end())) + cc = static_cast(it->second); + + return cc; + } + } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/src/debug/AllocationDebugger.cpp b/projs/shadow/shadow-engine/entity/src/debug/AllocationDebugger.cpp index cea100f5..1d672104 100644 --- a/projs/shadow/shadow-engine/entity/src/debug/AllocationDebugger.cpp +++ b/projs/shadow/shadow-engine/entity/src/debug/AllocationDebugger.cpp @@ -11,9 +11,9 @@ namespace SH::Entities::Debugger { auto &mgr = a->GetWorld().GetManager(); - for (auto allocator : mgr.m_NodeContainerRegistry) { - ImGui::Text("%s : %i", allocator.second->getTypeName().c_str(), allocator.second->getCount()); - } + //for (auto allocator : mgr.m_NodeContainerRegistry) { + // ImGui::Text("%s : %i", allocator.second->getTypeName().c_str(), allocator.second->getCount()); + //} } } diff --git a/projs/shadow/shadow-engine/entity/src/graph/graph.cpp b/projs/shadow/shadow-engine/entity/src/graph/graph.cpp index d7df541f..f70aad33 100644 --- a/projs/shadow/shadow-engine/entity/src/graph/graph.cpp +++ b/projs/shadow/shadow-engine/entity/src/graph/graph.cpp @@ -1,81 +1,58 @@ -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" +#include "shadow/entitiy/graph/managers.h" namespace SH::Entities { - SHObject_Base_Impl(NodeBase) - - void NodeBase::SetParent(const rtm_ptr &parent) { - this->parent = parent; - } - - void NodeBase::SetScene(const rtm_ptr &scene) { - this->m_scene = scene; - } - - void NodeBase::SetWorld(World *world) { - this->m_world = world; - } - - void NodeBase::Destroy() { - this->m_world->Destroy(this); - } - - SHObject_Base_Impl(LeafNode) - - SHObject_Base_Impl(Component) - - SHObject_Base_Impl(Node) - - void Node::AddChild(const rtm_ptr &child, bool internal) { - if (internal) - this->internal_hierarchy.push_back(child); - else - this->hierarchy.push_back(child); - // set the child's parent to this - child->SetParent(this); - // set the child's scene to this - child->SetScene(this->m_scene); - } - - void Node::RemoveChild(const rtm_ptr &child, bool internal) { - if (internal) - this->internal_hierarchy.erase( - std::remove(this->internal_hierarchy.begin(), - this->internal_hierarchy.end(), child), - this->internal_hierarchy.end()); - else - this->hierarchy.erase(std::remove(this->hierarchy.begin(), this->hierarchy.end(), child), - this->hierarchy.end()); - } - - SHObject_Base_Impl(Actor) - - SHObject_Base_Impl(Entity) - - SHObject_Base_Impl(Scene) - - SHObject_Base_Impl(RootNode) - - SHObject_Base_Impl(World) - - World::World() : systemManager(manager) { - this->root = manager.ConstructNode(); - this->root->SetWorld(this); - } - - void World::Destroy(NodeBase *node) { - // destroy all children - if (Node *n = dynamic_cast(node)) { - for (auto &child : n->GetHierarchy()) { - Destroy(child.Get()); - } - } - - node->GetParent()->RemoveChild(node); - - // destroy the node - manager.DestroyNode(node); - - } + SHObject_Base_Impl(NodeBase) + + void NodeBase::SetParent(const rtm_ptr &parent) { + this->parent = parent; + } + + void NodeBase::SetScene(const rtm_ptr &scene) { + this->m_scene = scene; + } + + void NodeBase::SetWorld(World *world) { + this->m_world = world; + } + + void NodeBase::Destroy() { + this->m_world->Destroy(this); + } + + SHObject_Base_Impl(Component) + + SHObject_Base_Impl(Node) + + void Node::AddChild(const rtm_ptr &child, bool internal) { + if (internal) { + this->internal_hierarchy.push_back(child); + } else { + this->hierarchy.push_back(child); + } + // set the child's parent to this + child->SetParent(this); + // set the child's scene to this + child->SetScene(this->m_scene); + } + + void Node::RemoveChild(const rtm_ptr &child, bool internal) { + if (internal) { + this->internal_hierarchy.erase( + std::remove(this->internal_hierarchy.begin(), + this->internal_hierarchy.end(), child), + this->internal_hierarchy.end()); + } else { + this->hierarchy.erase(std::remove(this->hierarchy.begin(), this->hierarchy.end(), child), + this->hierarchy.end()); + } + } + + SHObject_Base_Impl(Actor) + + SHObject_Base_Impl(Entity) + + SHObject_Base_Impl(Scene) } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/src/graph/managers.cpp b/projs/shadow/shadow-engine/entity/src/graph/managers.cpp new file mode 100644 index 00000000..a6bba83e --- /dev/null +++ b/projs/shadow/shadow-engine/entity/src/graph/managers.cpp @@ -0,0 +1,77 @@ +#include "shadow/entitiy/graph/managers.h" + +std::size_t std::hash::operator()(const SH::Entities::Types &s) const noexcept { + auto hash = SH::StableHash(s.data(), s.size()); + return hash.getHash(); +} + +namespace SH::Entities { + + template + void *NodeManager::get_component(const EntityId &entity) { + return get_component(entity, Node::TypeId()); + } + + void NodeManager::add_component(const EntityId &entity, const NodeBase &child) { + Types types = std::vector(entity_index.at(entity).archetype->types); + types.push_back(child.GetTypeId()); + + auto archetype = get_archetype(types); + + } + + const NodeManager::Archetype &NodeManager::get_archetype(const Types types) { + if(archetype_index.contains(types)){ + return *archetype_index.at(types); + } + else{ + auto &a = archetype.emplace_back(Archetype(next_archetype_id++)); + archetype_index.emplace(types, &a); + for (auto &type: types) { + + if(!component_index.contains(type)){ + component_index.insert({type, ArchetypeMap{}}).first->second; + } + + auto &archetypeList = component_index.at(type); + archetypeList.insert({a.id,{0}}); + } + } + } + + void* NodeManager::get_component(const EntityId &entity, const ComponentTypeId &component) { + Record &record = entity_index[entity]; + Archetype *archetype = record.archetype; + ArchetypeMap archetypes = component_index[component]; + if (archetypes.count(archetype->id) == 0) { + return nullptr; + } + ArchetypeRecord &a_record = archetypes[archetype->id]; + return archetype->children[a_record.column][record.row]; + } + + SHObject_Base_Impl(RootNode) + + SHObject_Base_Impl(World) + + World::World() : systemManager(manager) { + //this->root = manager.ConstructNode(); + //this->root->SetWorld(this); + } + + void World::Destroy(NodeBase *node) { + // destroy all children + if (Node * n = dynamic_cast(node)) { + for (auto &child : n->GetHierarchy()) { + Destroy(child.Get()); + } + } + + node->GetParent()->RemoveChild(node); + + // destroy the node + //manager.DestroyNode(node); + + } + +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/entity/tests/managers.test.cpp b/projs/shadow/shadow-engine/entity/tests/managers.test.cpp new file mode 100644 index 00000000..160a56cb --- /dev/null +++ b/projs/shadow/shadow-engine/entity/tests/managers.test.cpp @@ -0,0 +1,14 @@ +#include "catch2/catch.hpp" + +#include "shadow/entitiy/graph/managers.h" +#include "shadow/entitiy/entities/NullActor.h" + +TEST_CASE("NodeManager - get_component") { +// Set up the test environment + + SECTION("Getting a non-existent component should return nullptr") { + auto manager = SH::Entities::NodeManager(); + manager.AddNode(SH::Entities::Builtin::NullActor{}); + + } +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/reflection/inc/shadow/SHObject.h b/projs/shadow/shadow-engine/reflection/inc/shadow/SHObject.h index 49671459..6e1e659d 100644 --- a/projs/shadow/shadow-engine/reflection/inc/shadow/SHObject.h +++ b/projs/shadow/shadow-engine/reflection/inc/shadow/SHObject.h @@ -7,7 +7,8 @@ namespace SH { - typedef uint64_t TypeID; + //typedef uint64_t TypeId; + using TypeId = uint64_t; /** * \brief This is the base class for every class in the Engine that uses runtime reflection. @@ -27,7 +28,7 @@ namespace SH { * \brief Generates a new UID for each call * \return the next Unique ID that was just generated */ - API static TypeID GenerateId() noexcept; + API static TypeId GenerateId() noexcept; public: /** @@ -40,7 +41,7 @@ namespace SH { * \brief Gets the top level type ID * \return UID of the class */ - virtual TypeID GetTypeId() const = 0; + virtual TypeId GetTypeId() const = 0; virtual ~SHObject() = default; }; @@ -55,13 +56,13 @@ namespace SH { #define SHObject_Base(type) \ public: \ static const std::string& Type(); \ - static SH::TypeID TypeId(); \ + static SH::TypeId TypeId(); \ const std::string& GetType() const override { return Type(); } \ - SH::TypeID GetTypeId() const override { return type::TypeId(); } \ + SH::TypeId GetTypeId() const override { return type::TypeId(); } \ private: #define SHObject_Base_Impl(type) \ const std::string& type::Type() { static const std::string t = typeid(type).name(); return t; } \ - SH::TypeID type::TypeId() { static const SH::TypeID id = GenerateId(); return id; } + SH::TypeId type::TypeId() { static const SH::TypeId id = GenerateId(); return id; } } \ No newline at end of file diff --git a/projs/shadow/shadow-engine/utility/inc/shadow/util/UUID.h b/projs/shadow/shadow-engine/utility/inc/shadow/util/UUID.h new file mode 100644 index 00000000..891b9262 --- /dev/null +++ b/projs/shadow/shadow-engine/utility/inc/shadow/util/UUID.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SH::Util { + + /** + * Universally Unique ID. + * 128 Bits. + * + * Unique per runtime only - the suitability for serialization is undetermined. + */ + class UUID { + + /** + * Data storage; 128 bits. + * 2 x 64 bit + * 4 x 32 bit + * 16 x 8 bit + */ + union Data { + uint64_t u64[2]; + uint32_t u32[4]; + uint8_t u8[16]; + }; + + public: + // Create a new, unused, UUID. + static UUID Generate(); + // Check whether the UUID is correctly formed. + static bool IsValidStr(char const *str); + + // Create an empty UUID. + inline UUID() { std::memset(&data.u8, 0, 16); } + // Create a UUID based on the given values. + inline UUID(uint64_t i0, uint64_t i1) { + data.u64[0] = i0; + data.u64[1] = i1; + } + inline UUID(uint32_t i0, uint32_t i1, uint32_t i2, uint32_t i3) { + data.u32[0] = i0; + data.u32[1] = i1; + data.u32[2] = i2; + data.u32[3] = i3; + } + inline explicit UUID(std::string const &str) : UUID(str.c_str()) {} + // Create a UUID from the given format. + explicit UUID(char const *str); + + // Check whether the UUID is nonzero. + inline bool IsValid() const { return data.u64[0] != 0 && data.u64[1] != 0; } + // Set the UUID to zero. + inline void Clear() { std::memset(&data.u8, 0, 16); } + + // Get a section of the UUID's data as the given bit width. + inline uint8_t GetU8(size_t idx) const { return data.u8[idx]; } + inline uint32_t GetU32(size_t idx) const { return data.u32[idx]; } + inline uint64_t GetU64(size_t idx) const { return data.u64[idx]; } + + // Check whether this and a given UUID are in/equal. + __inline bool operator==(UUID const &other) const { + return data.u64[0] == other.data.u64[0] && data.u64[1] == other.data.u64[1]; + } + __inline bool operator!=(UUID const &other) const { return !(*this == other); } + + private: + + Data data{}; + }; +} \ No newline at end of file diff --git a/projs/shadow/shadow-engine/utility/src/UUID.cpp b/projs/shadow/shadow-engine/utility/src/UUID.cpp new file mode 100644 index 00000000..8f3092f5 --- /dev/null +++ b/projs/shadow/shadow-engine/utility/src/UUID.cpp @@ -0,0 +1,56 @@ +#include "shadow/util/UUID.h" + +namespace SH::Util { + static_assert(sizeof(UUID) == 16, "UUID has incorrect size"); + + /** + * Verify that a string has the correct format; + * XXXXXXXX-XXXX-XXX-XXXXX-XXXXXXXXXXXX + * + * The length must be 36. + * There must be dashes at index 8, 13, 18 and 23. + * @param str the input string + * @return whether the UUID string is correctly formed + */ + bool UUID::IsValidStr(const char *str) { + size_t const len = strlen(str); + if (len != 36) return false; + + for (size_t i = 0; i < len; i++) { + char c = str[i]; + if (c == '-') { + if (i != 8 && i != 13 && i != 18 && i != 23) return false; + } else if (!std::isxdigit(c)) { + return false; + } + } + + return true; + } + + UUID::UUID(char const *str) { + // A single byte is two hex characters. + // Store them here so that we can use them later. + char c0 = '\0', c1; + + size_t const len = strlen(str); + uint32_t byteIdx = 0; + + for (size_t i = 0; i < len; i++) { + char const c = str[i]; + if (c == '-') + continue; + + // Scan for pairs of characters. + // Only assign a byte if two have been parsed. + if (c0 == '\0') { + c0 = c; + } else { + c1 = c; + data.u8[byteIdx++] = std::stoi(std::string(c0, c1)); + // Reset the first char so that we can return to scanning a pair. + c0 = '\0'; + } + } + } +} \ No newline at end of file diff --git a/projs/test-game/inc/TestScene.h b/projs/test-game/inc/TestScene.h index 0b22ae88..8ae9804d 100644 --- a/projs/test-game/inc/TestScene.h +++ b/projs/test-game/inc/TestScene.h @@ -1,11 +1,11 @@ #pragma once -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" class TestScene : public SH::Entities::Scene { - SHObject_Base(TestScene) - public: - TestScene() : Scene("Test scene") {} +SHObject_Base(TestScene) +public: + TestScene() : Scene("Test scene") {} - void Build() override; + void Build() override; }; diff --git a/projs/test-game/inc/entities/Health.h b/projs/test-game/inc/entities/Health.h index 07ca1032..e42513a2 100644 --- a/projs/test-game/inc/entities/Health.h +++ b/projs/test-game/inc/entities/Health.h @@ -1,9 +1,9 @@ #pragma once -#include "shadow/entitiy/graph//graph.h" +#include "shadow/entitiy/graph//nodes.h" class Health : public SH::Entities::Component { - SHObject_Base(Health); - public: - int health; +SHObject_Base(Health); +public: + int health; }; diff --git a/projs/test-game/inc/entities/Player.h b/projs/test-game/inc/entities/Player.h index 1db8581e..a6d4ff56 100644 --- a/projs/test-game/inc/entities/Player.h +++ b/projs/test-game/inc/entities/Player.h @@ -1,14 +1,14 @@ #pragma once -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" class Player : public SH::Entities::Actor { - SHObject_Base(Player); +SHObject_Base(Player); - public: - Player(std::string name) : Actor() { - this->name = name; - } +public: + Player(std::string name) : Actor() { + this->name = name; + } - void Build() override; + void Build() override; }; diff --git a/projs/test-game/inc/entities/TestCamera.h b/projs/test-game/inc/entities/TestCamera.h index 119cfa4a..854b3f2a 100644 --- a/projs/test-game/inc/entities/TestCamera.h +++ b/projs/test-game/inc/entities/TestCamera.h @@ -1,11 +1,11 @@ #pragma once -#include "shadow/entitiy/graph/graph.h" +#include "shadow/entitiy/graph/nodes.h" //Example of a 2d camera component with size class TestCamera : public SH::Entities::Component { - SHObject_Base(TestCamera); - public: - float width; - float height; +SHObject_Base(TestCamera); +public: + float width; + float height; }; From f86292ec5b6ae3a18cd3e99c2c972df31649b1cc Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Fri, 15 Sep 2023 10:38:11 +0200 Subject: [PATCH 02/19] [SYNC] --- projs/experiments/ecs/src/ecs.exp.h | 58 ++++++++++++++++++++++++++++- projs/experiments/ecs/src/main.cpp | 4 +- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 7caa22eb..39596c3e 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -243,12 +243,34 @@ class Object { }; -class Entity { +class Component : public Object{ + +}; + +class Entity : public Component { }; template concept entity = std::is_base_of_v; + + + + + + +class Scene : public Entity{ + std::string name = "Test"; +}; + + + + +//#################################################### +//################## ID system ####################### +//#################################################### +#pragma region ID + union Id { std::byte bytes[sizeof(uint64_t)]; uint64_t id; @@ -263,6 +285,9 @@ struct std::hash { }; }; +bool operator==(const Id &lhs, const Id &rhs) { return lhs.id == rhs.id; } + + TypeId next_id; template @@ -272,7 +297,11 @@ TypeId getTypeId() { return id; } +#pragma endregion ID + + class Archetype { +public: using Id = uint32_t; static Id next_id; @@ -287,7 +316,7 @@ class Archetype { class EntityManager { - std::unordered_map pools = {}; + std::unordered_map pools; template SH::PoolAllocator &GetPool(TypeId type) { @@ -297,6 +326,14 @@ class EntityManager { return pools.at(type); } + + std::unordered_map archetypes; + + Archetype& GetArchetype(Archetype arc){ + + } + +public: template Ent *AddChild(Entity &parent) { // Allocate the space for the entity @@ -310,6 +347,23 @@ class EntityManager { return entity; } + template + Ent *Add() { + // Allocate the space for the entity + SH::PoolAllocator &pool = GetPool(getTypeId()); + void *pos = pool.allocate(); + + // Create it + Ent *entity = new(pos)Ent(); + + //Find or make archetype + Archetype &a = GetArchetype(Archetype({getTypeId()})); + + + + return entity; + } + }; Archetype::Id Archetype::next_id = 0; \ No newline at end of file diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp index 071efa57..d6cbda97 100644 --- a/projs/experiments/ecs/src/main.cpp +++ b/projs/experiments/ecs/src/main.cpp @@ -15,5 +15,7 @@ class Position { int main() { EntityManager em; - em.AddChild<>() + Scene *scene = em.Add(); + + return 0; } \ No newline at end of file From bd8c8dfd6b1d74b720dabd21aac01fbce723abcf Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Sun, 17 Sep 2023 21:28:05 +0200 Subject: [PATCH 03/19] First working of the ecs experiment --- .gdbinit | 1 + .gitmodules | 6 +- deps.cmake | 16 + experiment-ecs-printer.py | 42 ++ projs/experiments/ecs/CMakeLists.txt | 5 + projs/experiments/ecs/src/ecs.exp.h | 376 +++++++++++++++--- projs/experiments/ecs/src/main.cpp | 27 +- projs/extern/fmt | 1 + .../renderer/Vulkan/src/render/Camera.cpp | 40 +- 9 files changed, 431 insertions(+), 83 deletions(-) create mode 100644 .gdbinit create mode 100644 deps.cmake create mode 100644 experiment-ecs-printer.py create mode 160000 projs/extern/fmt diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 00000000..703993c7 --- /dev/null +++ b/.gdbinit @@ -0,0 +1 @@ +source ./experiment-ecs-printer.py \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 3fc45167..ca598af3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,4 +18,8 @@ url = https://github.com/martin-olivier/dylib [submodule "projs/shadow/extern/vulkan_memory_allocator"] path = projs/shadow/extern/vulkan_memory_allocator - url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git \ No newline at end of file + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git + +[submodule "extern/fmt"] + path = projs/extern/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/deps.cmake b/deps.cmake new file mode 100644 index 00000000..3d07a22a --- /dev/null +++ b/deps.cmake @@ -0,0 +1,16 @@ +Include(FetchContent) +include(ExternalProject) + + +add_subdirectory(projs/extern/fmt) + +# ############################################### +# Fetch Catch2 for the file format tests +# ############################################### +#FetchContent_Declare( +# Catch2 +# GIT_REPOSITORY https://github.com/catchorg/Catch2.git +# GIT_TAG v2.13.9 # or a later release +#) +#FetchContent_MakeAvailable(Catch2) +#list(APPEND CMAKE_MODULE_PATH "${Catch2_SOURCE_DIR}/contrib") diff --git a/experiment-ecs-printer.py b/experiment-ecs-printer.py new file mode 100644 index 00000000..da24745c --- /dev/null +++ b/experiment-ecs-printer.py @@ -0,0 +1,42 @@ +import gdb.printing + + +class idPrinter: + """Print a foo object.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + return (str(self.val["id"]) + + " | " + str(self.val["half"][0]) + "," + str(self.val["half"][1])) + + +# return self.val["id"] + + +# def children(self): +# yield "full", self.val["id"] +# yield "half", self.val["half"] + + +class archetypePrinter: + """Print a foo object.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val["id"] + + +def build_pretty_printer(): + pp = gdb.printing.RegexpCollectionPrettyPrinter("experiment-ecs") + pp.add_printer('id', '^Id$', idPrinter) + # pp.add_printer('archetype', '^Archetype$', archetypePrinter) + return pp + + +gdb.printing.register_pretty_printer( + gdb.current_objfile(), + build_pretty_printer()) diff --git a/projs/experiments/ecs/CMakeLists.txt b/projs/experiments/ecs/CMakeLists.txt index b6bc3152..d4b0934b 100644 --- a/projs/experiments/ecs/CMakeLists.txt +++ b/projs/experiments/ecs/CMakeLists.txt @@ -10,6 +10,11 @@ FILE(GLOB_RECURSE SOURCES target_sources(experiment-ecs PUBLIC ${SOURCES}) +target_link_libraries(experiment-ecs + PRIVATE fmt::fmt-header-only +) + + FILE(GLOB_RECURSE SOURCES_TESTS tests/*.cpp ) diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 39596c3e..8ab8d74a 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -7,22 +7,26 @@ #include #include #include +#include + +#include //#include "../../shadow/shadow-engine/reflection/inc/shadow/SHObject.h" //#include "../../shadow/shadow-engine/core/inc/shadow/exports.h" + namespace SH { class span_dynamic { std::byte *p_start; std::byte *p_end; - size_t itemSize; + size_t item_size; public: span_dynamic() = default; span_dynamic(void *mem, size_t item_size, size_t count) : p_start(static_cast(mem)), p_end(p_start + (item_size * count)), - itemSize(item_size) { + item_size(item_size) { assert((p_end - p_start) / item_size == count); } @@ -39,12 +43,12 @@ namespace SH { iterator() : pos(nullptr), size(0) {}; iterator(std::byte *p, size_t item_size) : pos(p), size(item_size) {}; - void move(size_t n) { + void Move(size_t n) { pos += (n * size); } iterator &operator++() { - move(1); + Move(1); return *this; } iterator operator++(int) { @@ -54,7 +58,7 @@ namespace SH { } iterator &operator--() { - move(-1); + Move(-1); return *this; } iterator operator--(int) { @@ -64,28 +68,28 @@ namespace SH { } iterator &operator+=(difference_type n) { - move(n); + Move(n); return *this; } iterator &operator-=(difference_type n) { - move(-n); + Move(-n); return *this; } iterator operator+(const difference_type &n) const { iterator tmp(*this); - tmp.move(n); + tmp.Move(n); return tmp; } iterator operator-(const difference_type &n) const { iterator tmp(*this); - tmp.move(-n); + tmp.Move(-n); return tmp; } std::byte &operator[](const difference_type &n) const { iterator tmp(*this); - tmp.move(n); + tmp.Move(n); return *tmp; } @@ -106,15 +110,15 @@ namespace SH { template T *as_ptr() { return (T *) pos; } - void *ptr() const { return pos; } + [[nodiscard]] void *ptr() const { return pos; } }; - iterator begin() const { - return {p_start, itemSize}; + [[nodiscard]] iterator begin() const { + return {p_start, item_size}; } iterator end() const { - return {p_end, itemSize}; + return {p_end, item_size}; } iterator last() const { @@ -124,12 +128,12 @@ namespace SH { span_dynamic::iterator operator+(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { span_dynamic::iterator tmp(i); - tmp.move(n); + tmp.Move(n); return tmp; } span_dynamic::iterator operator-(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { span_dynamic::iterator tmp(i); - tmp.move(-n); + tmp.Move(-n); return tmp; } @@ -239,78 +243,166 @@ namespace SH { * * */ -class Object { -}; +//#################################################### +//################## ID system ####################### +//#################################################### +#pragma region ID-System -class Component : public Object{ +union Id { + std::byte bytes[sizeof(uint64_t)]; + uint32_t half[2]; + uint64_t id; + + Id(uint64_t id) : id(id) {}; + Id(uint32_t high, uint32_t low) : half{high, low} {}; + Id Next() { + return Id(this->id++); + } }; -class Entity : public Component { +using TypeId = Id; +template<> +struct std::hash { + std::size_t operator()(const TypeId &s) const noexcept { + return std::hash{}(s.id); + }; }; -template -concept entity = std::is_base_of_v; +bool operator==(const Id &lhs, const Id &rhs) { return lhs.id == rhs.id; } +std::unordered_map typeMap; +TypeId next_id(0); +template +TypeId GetTypeId() { + static TypeId id = next_id.id++; + static auto a = typeMap.insert({id, typeid(T).name()}); + return id; +} + +#pragma endregion ID-System +//#################################################### +//################## Entity base classes ############# +//#################################################### +class Object { -class Scene : public Entity{ - std::string name = "Test"; }; +class Component : public Object { +public: + static constexpr bool isEntity = false; + Id UUID; + Component(Id UUID) : UUID(UUID) {}; +}; +template +concept component = std::is_base_of_v; +template +concept component_only = component && T::isEntity == false; + +class Entity : public Component { +public: + static constexpr bool isEntity = true; + Entity(Id UUID) : Component(UUID) { + + } + + template + T *AddInternalChild(); + +}; +template +concept entity = std::is_base_of_v && T::isEntity == true; //#################################################### -//################## ID system ####################### +//############### Prefab stuff ####################### //#################################################### -#pragma region ID -union Id { - std::byte bytes[sizeof(uint64_t)]; - uint64_t id; +class Asset { + }; -using TypeId = Id; +class Prefab : public Asset { -template<> -struct std::hash { - std::size_t operator()(const TypeId &s) const noexcept { - return std::hash{}(s.id); - }; }; -bool operator==(const Id &lhs, const Id &rhs) { return lhs.id == rhs.id; } +class PrefabEntity : public Entity { + Prefab p; +public: + PrefabEntity(Id UUID, Prefab asset) : Entity(UUID), p(asset) { + } -TypeId next_id; +}; -template -TypeId getTypeId() { - static TypeId id = next_id; - id.id++; - return id; -} +//#################################################### +//################Built in entities ################## +//#################################################### -#pragma endregion ID +class Scene : public Entity { + std::string name = "Test"; +public: + Scene(Id UUID) : Entity(UUID) {}; +}; +enum Relation { + PARENT = 1, +}; class Archetype { public: using Id = uint32_t; static Id next_id; - std::vector types; + using Types = std::vector; + + Types types; Id id; + + using Column = std::vector; + + std::vector data = std::vector(0); + + static Types sortTypes(Types t) { + std::ranges::sort(t, [](auto a, auto b) { return a.id < b.id; }); + return t; + } public: Archetype() = default; - Archetype(std::initializer_list types) : types(types), id(next_id++) { + Archetype(std::initializer_list types) : types(sortTypes(types)), id(next_id++) {} + Archetype(Types types) : types(sortTypes(types)), id(next_id++) {} + + std::pair AddRow() { + auto &row = data.emplace_back(Column(types.size())); + return {data.size() - 1, row}; + } + + size_t getColumn(TypeId column_type) { + auto a = std::ranges::find(types, column_type); + size_t pos = std::distance(types.begin(), a); + return pos; + } + void RemoveRow(size_t i) { + data.erase(data.begin() + i); + }; +}; + +template<> +struct std::hash { + std::size_t operator()(const Archetype::Types &vec) const noexcept { + std::size_t seed = vec.size(); + for (auto &i : vec) { + seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; } }; @@ -318,52 +410,214 @@ class EntityManager { std::unordered_map pools; - template + template SH::PoolAllocator &GetPool(TypeId type) { if (!pools.contains(type)) { - pools.insert({type, SH::PoolAllocator(sizeof(Ent))}); + pools.emplace(type, SH::PoolAllocator(sizeof(Ent))); } return pools.at(type); } + std::unordered_map archetypes; - std::unordered_map archetypes; + struct ArchetypeRecord { + Archetype *archetype; + size_t row; + }; - Archetype& GetArchetype(Archetype arc){ + std::unordered_map entity_archetype; + Archetype &GetArchetype(Archetype arc) { + if (archetypes.contains(arc.types)) { + return archetypes.at(arc.types); + } else { + auto res = archetypes.emplace(arc.types, arc).first; + return res->second; + } + } + + ArchetypeRecord &GetEntityArchetype(Entity &ent) { + return entity_archetype.at(ent.UUID); + } + + uint32_t nextUUID = 0; + Id GetNewUUID() { + return Id(nextUUID++, 0); + } + + template + void *AllocateForNew() { + SH::PoolAllocator &pool = GetPool(GetTypeId()); + void *pos = pool.allocate(); + return pos; } public: + static EntityManager *entity_manager; + + EntityManager() { + entity_manager = this; + } + + template + Comp *AddChild(Entity &parent) { + // Allocate the space for the entity + const TypeId &comp_id = GetTypeId(); + + void *memory = AllocateForNew(); + + //Find old archetype + auto &record = GetEntityArchetype(parent); + auto &old_arch = *record.archetype; + + //Construct new type list and find the archetype + Archetype::Types types(old_arch.types); + types.emplace_back(comp_id); + + Archetype &new_arch = GetArchetype(Archetype(types)); + + auto new_row = new_arch.AddRow(); + auto &row = new_row.second; + + for (size_t i = 0; i < old_arch.types.size(); i++) { + auto column_type = old_arch.types[i]; + size_t pos = new_arch.getColumn(column_type); + row[pos] = old_arch.data[record.row][i]; + } + row[new_arch.getColumn(comp_id)] = (Comp *) memory; + + old_arch.RemoveRow(record.row); + + record.row = new_row.first; + record.archetype = &new_arch; + // Create it + Comp *component = new(memory)Comp(GetNewUUID()); + + // Add it to the parent + return component; + } + template Ent *AddChild(Entity &parent) { + const TypeId &type_id = GetTypeId(); + // Allocate the space for the entity - SH::PoolAllocator &pool = GetPool(getTypeId()); - void *pos = pool.allocate(); + void *pos = AllocateForNew(); + + //Find or make archetype + Archetype &a = GetArchetype( + Archetype({ + type_id, + Id(parent.UUID.half[0], Relation::PARENT) + })); + + auto &row = a.AddRow().second; + row[a.getColumn(type_id)] = (Ent *) pos; + + Id Uuid = GetNewUUID(); + + entity_archetype.insert({Uuid, ArchetypeRecord{&a, a.getColumn(type_id)}}); // Create it - Ent *entity = new(pos)Ent(); + Ent *entity = new(pos)Ent(Uuid); - // Add it to the parent return entity; } + /* + * Add a fresh new entity + */ template Ent *Add() { // Allocate the space for the entity - SH::PoolAllocator &pool = GetPool(getTypeId()); - void *pos = pool.allocate(); - - // Create it - Ent *entity = new(pos)Ent(); + void *pos = AllocateForNew(); //Find or make archetype - Archetype &a = GetArchetype(Archetype({getTypeId()})); + Archetype &a = GetArchetype(Archetype({GetTypeId()})); + + auto &row = a.AddRow().second; + row[0] = (Ent *) pos; + + Id Uuid = GetNewUUID(); + entity_archetype.insert({Uuid, ArchetypeRecord{&a, a.getColumn(GetTypeId())}}); + // Create it + Ent *entity = new(pos)Ent(Uuid); return entity; } + #pragma region DumpData + std::string GetTypeOrRelationName(Id type_id) { + std::string res = ""; + if (type_id.half[1] > 0) { + switch (type_id.half[1]) { + case Relation::PARENT:res += "Parent: "; + break; + default:break; + } + } + res += typeMap.at(type_id.half[0]); + return res; + } + + void DumpData() { + + { + std::printf("Pools: \n"); + for (auto &pool : pools) { + fmt::print("{0:20} count:{1}\n", typeMap.at(pool.first), "??"); + } + std::printf("\n"); + } + + { + std::printf("Archetypes: \n"); + size_t max_len = 0; + for (auto &arch : archetypes) { + std::vector target(arch.second.types.size()); + for (int i = 0; i < arch.second.types.size(); ++i) { + target[i] = GetTypeOrRelationName(arch.second.types[i]); + } + max_len = std::max(max_len, fmt::format("{}", fmt::join(target, " | ")).size()); + } + + for (auto &arch : archetypes) { + std::printf("%i :( ", arch.second.id); + + std::vector target(arch.second.types.size()); + for (int i = 0; i < arch.second.types.size(); ++i) { + target[i] = GetTypeOrRelationName(arch.second.types[i]); + } + + fmt::print("{0:<{1}} )\t\t", fmt::format("{}", fmt::join(target, " | ")), max_len); + fmt::print("count: {}", arch.second.data.size()); + + fmt::print("\n"); + } + fmt::print("\n"); + } + + std::printf("Entity map: \n"); + for (auto &data : entity_archetype) { + + fmt::print("ID: {0}, (row: {1}, arch: {2})", data.first.id, data.second.row, data.second.archetype->id); + + fmt::println(""); + } + } + #pragma endregion DumpData + }; -Archetype::Id Archetype::next_id = 0; \ No newline at end of file +template +T *Entity::AddInternalChild() { + return EntityManager::entity_manager->AddChild(*this); +} + +EntityManager *EntityManager::entity_manager = nullptr; + +Archetype::Id Archetype::next_id = 0; + + diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp index d6cbda97..07ff22a3 100644 --- a/projs/experiments/ecs/src/main.cpp +++ b/projs/experiments/ecs/src/main.cpp @@ -3,19 +3,42 @@ // #include "ecs.exp.h" -class Player{ +class Position : public Component { +public: + float x; + float y; + float z; +}; + +class Mesh : public Component { }; -class Position { +class HP : public Component { +public: + int Hp; +}; + +class Player : public Entity { +public: + Player(Id UUID) : Entity(UUID) { + auto *p = this->AddInternalChild(); + p->x = 10; + this->AddInternalChild(); + this->AddInternalChild(); + } }; +static_assert(Scene::isEntity == true); int main() { EntityManager em; Scene *scene = em.Add(); + Player *player = em.AddChild(*scene); + + em.DumpData(); return 0; } \ No newline at end of file diff --git a/projs/extern/fmt b/projs/extern/fmt new file mode 160000 index 00000000..a8a73da7 --- /dev/null +++ b/projs/extern/fmt @@ -0,0 +1 @@ +Subproject commit a8a73da7e44e26d7c18d752976522eff7a21e0bf diff --git a/projs/shadow/shadow-engine/renderer/Vulkan/src/render/Camera.cpp b/projs/shadow/shadow-engine/renderer/Vulkan/src/render/Camera.cpp index ec6b9efc..92ff0602 100644 --- a/projs/shadow/shadow-engine/renderer/Vulkan/src/render/Camera.cpp +++ b/projs/shadow/shadow-engine/renderer/Vulkan/src/render/Camera.cpp @@ -2,7 +2,7 @@ using namespace vlkx; -Camera& Camera::move(const glm::vec3 &delta) { +Camera &Camera::move(const glm::vec3 &delta) { position += delta; return *this; } @@ -35,7 +35,7 @@ PerspectiveCamera &PerspectiveCamera::fieldOfView(float newFov) { PerspectiveCamera::RT PerspectiveCamera::getRT() const { const glm::vec3 upVec = glm::normalize(glm::cross(getRight(), getForward())); const float fovTan = glm::tan(glm::radians(fov)); - return { upVec * fovTan, getForward(), getRight() * fovTan * aspectRatio }; + return {upVec * fovTan, getForward(), getRight() * fovTan * aspectRatio}; } glm::mat4 PerspectiveCamera::getProjMatrix() const { @@ -51,17 +51,17 @@ OrthographicCamera &OrthographicCamera::setWidth(float vWidth) { glm::mat4 OrthographicCamera::getProjMatrix() const { const float height = width / aspectRatio; - const auto halfSize = glm::vec2 { width, height } / 2.0f; + const auto halfSize = glm::vec2{width, height} / 2.0f; return glm::ortho(-halfSize.x, halfSize.x, -halfSize.y, halfSize.y, nearPlane, farPlane); } -template +template void UserCamera::setInternal(std::function op) { op(camera.get()); reset(); } -template +template void UserCamera::move(double x, double y) { if (!isActive) return; @@ -70,10 +70,10 @@ void UserCamera::move(double x, double y) { pitch = glm::clamp(pitch - offsetY, glm::radians(-89.9f), glm::radians(89.9f)); yaw = glm::mod(yaw - offsetX, glm::radians(360.0f)); - camera->forward( { glm::cos(pitch) * glm::cos(yaw), glm::sin(pitch), glm::cos(pitch) * glm::sin(yaw) }); + camera->forward({glm::cos(pitch) * glm::cos(yaw), glm::sin(pitch), glm::cos(pitch) * glm::sin(yaw)}); } -template +template bool UserCamera::scroll(double delta, double min, double max) { if (!isActive) return false; @@ -96,7 +96,7 @@ bool UserCamera::scroll(double delta, double min, double max) { return false; } -template +template void UserCamera::press(Camera::Input key, float time) { using Key = Camera::Input; if (!isActive) return; @@ -104,26 +104,28 @@ void UserCamera::press(Camera::Input key, float time) { if (!config.center.has_value()) { const float distance = time * config.moveSpeed; switch (key) { - case Key::Up: - camera->move(+camera->getForward() * distance); break; - case Key::Down: - camera->move(-camera->getForward() * distance); break; - case Key::Left: - camera->move(-camera->getRight() * distance); break; - case Key::Right: - camera->move(+camera->getRight() * distance); break; + case Key::Up:camera->Move(+camera->getForward() * distance); + break; + case Key::Down:camera->Move(-camera->getForward() * distance); + break; + case Key::Left:camera->Move(-camera->getRight() * distance); + break; + case Key::Right:camera->Move(+camera->getRight() * distance); + break; } } else { reset(); } } -template +template void UserCamera::reset() { refForward = camera->getForward(); refLeft = -camera->getRight(); pitch = yaw = 0; } -template class vlkx::UserCamera; -template class vlkx::UserCamera; \ No newline at end of file +template +class vlkx::UserCamera; +template +class vlkx::UserCamera; \ No newline at end of file From 3a20d4380bb3ec7931de8da77de86b37a2ce327c Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Mon, 18 Sep 2023 21:12:35 +0200 Subject: [PATCH 04/19] Add helper function for moving between archetypes --- projs/experiments/ecs/src/ecs.exp.h | 37 ++++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 8ab8d74a..2ddbcb0a 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -386,7 +386,10 @@ class Archetype { size_t getColumn(TypeId column_type) { auto a = std::ranges::find(types, column_type); - size_t pos = std::distance(types.begin(), a); + if(a == types.end()) + return -1; + + size_t const pos = std::distance(types.begin(), a); return pos; } @@ -452,6 +455,23 @@ class EntityManager { return pos; } + void MoveToArchetype(ArchetypeRecord &src, Archetype &dst){ + auto dst_row = dst.AddRow(); + + for (size_t i = 0; i < src.archetype->types.size(); i++) { + auto column_type = src.archetype->types[i]; + size_t dest_column = dst.getColumn(column_type); + if(dest_column == -1) + continue; + dst_row.second[dest_column] = src.archetype->data[src.row][i]; + } + + src.archetype->RemoveRow(src.row); + + src.row = dst_row.first; + src.archetype = &dst; + } + public: static EntityManager *entity_manager; @@ -476,20 +496,9 @@ class EntityManager { Archetype &new_arch = GetArchetype(Archetype(types)); - auto new_row = new_arch.AddRow(); - auto &row = new_row.second; - - for (size_t i = 0; i < old_arch.types.size(); i++) { - auto column_type = old_arch.types[i]; - size_t pos = new_arch.getColumn(column_type); - row[pos] = old_arch.data[record.row][i]; - } - row[new_arch.getColumn(comp_id)] = (Comp *) memory; - - old_arch.RemoveRow(record.row); + MoveToArchetype(record, new_arch); - record.row = new_row.first; - record.archetype = &new_arch; + record.archetype->data[record.row][new_arch.getColumn(comp_id)] = (Comp *) memory; // Create it Comp *component = new(memory)Comp(GetNewUUID()); From afc2909e85e38add8f0f0c43b90354c604efca92 Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Sat, 23 Nov 2024 01:44:26 +0100 Subject: [PATCH 05/19] Work on the ECs experiment --- CMakeLists.txt | 2 + experiment-ecs-printer.py | 26 +- projs/experiments/ecs/CMakeLists.txt | 4 +- projs/experiments/ecs/src/_old/old_ecs.exp.h | 632 ++++++++++++++++++ projs/experiments/ecs/src/ecs.exp.cpp | 69 ++ projs/experiments/ecs/src/ecs.exp.h | 597 +---------------- projs/experiments/ecs/src/entity.h | 39 ++ projs/experiments/ecs/src/id_system.cpp | 31 + projs/experiments/ecs/src/id_system.h | 97 +++ projs/experiments/ecs/src/main.cpp | 43 +- projs/experiments/ecs/src/span_dynamic.cpp | 1 + projs/experiments/ecs/src/span_dynamic.h | 181 +++++ .../experiments/ecs/tests/archetype_tests.cpp | 10 + projs/experiments/ecs/tests/id_tests.cpp | 23 + .../ecs/tests/span_dynamic.test.cpp | 59 +- projs/shadow/extern/catch2 | 2 +- 16 files changed, 1142 insertions(+), 674 deletions(-) create mode 100644 projs/experiments/ecs/src/_old/old_ecs.exp.h create mode 100644 projs/experiments/ecs/src/ecs.exp.cpp create mode 100644 projs/experiments/ecs/src/entity.h create mode 100644 projs/experiments/ecs/src/id_system.cpp create mode 100644 projs/experiments/ecs/src/id_system.h create mode 100644 projs/experiments/ecs/src/span_dynamic.cpp create mode 100644 projs/experiments/ecs/src/span_dynamic.h create mode 100644 projs/experiments/ecs/tests/archetype_tests.cpp create mode 100644 projs/experiments/ecs/tests/id_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b31f47df..f06698ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ include(shadow-modules.cmake) set(CMAKE_STATIC_LIBRARY_PREFIX "") set(CMAKE_SHARED_LIBRARY_PREFIX "") +add_subdirectory(projs/extern/fmt) + add_subdirectory(projs/shadow) add_subdirectory(projs/test-game) diff --git a/experiment-ecs-printer.py b/experiment-ecs-printer.py index da24745c..420d1e97 100644 --- a/experiment-ecs-printer.py +++ b/experiment-ecs-printer.py @@ -2,38 +2,28 @@ class idPrinter: - """Print a foo object.""" + """Print a TypeId object.""" def __init__(self, val): self.val = val def to_string(self): - return (str(self.val["id"]) + - " | " + str(self.val["half"][0]) + "," + str(self.val["half"][1])) - - -# return self.val["id"] - - -# def children(self): -# yield "full", self.val["id"] -# yield "half", self.val["half"] - - -class archetypePrinter: - """Print a foo object.""" + return ("ID: " + str(self.val["id"]) + " Flags: " + str(self.val["flags"])) +class TypeIdPairPrinter: def __init__(self, val): self.val = val def to_string(self): - return self.val["id"] + id = idPrinter(self.val["first"]) + return (id.to_string() + " Column: " + "asd") def build_pretty_printer(): pp = gdb.printing.RegexpCollectionPrettyPrinter("experiment-ecs") - pp.add_printer('id', '^Id$', idPrinter) - # pp.add_printer('archetype', '^Archetype$', archetypePrinter) + pp.add_printer('id', '^TypeId$', idPrinter) + pp.add_printer('id_pair', '^TypeId, int$', TypeIdPairPrinter) + return pp diff --git a/projs/experiments/ecs/CMakeLists.txt b/projs/experiments/ecs/CMakeLists.txt index d4b0934b..f8c9012e 100644 --- a/projs/experiments/ecs/CMakeLists.txt +++ b/projs/experiments/ecs/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) add_executable(experiment-ecs) add_executable(experiment::ecs ALIAS experiment-ecs) @@ -16,6 +16,8 @@ target_link_libraries(experiment-ecs FILE(GLOB_RECURSE SOURCES_TESTS + src/id_system.cpp + src/span_dynamic.cpp tests/*.cpp ) diff --git a/projs/experiments/ecs/src/_old/old_ecs.exp.h b/projs/experiments/ecs/src/_old/old_ecs.exp.h new file mode 100644 index 00000000..fea888a0 --- /dev/null +++ b/projs/experiments/ecs/src/_old/old_ecs.exp.h @@ -0,0 +1,632 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//#include "../../shadow/shadow-engine/reflection/inc/shadow/SHObject.h" +//#include "../../shadow/shadow-engine/core/inc/shadow/exports.h" + + +namespace SH { + + class span_dynamic { + std::byte *p_start; + std::byte *p_end; + size_t item_size; + public: + span_dynamic() = default; + span_dynamic(void *mem, size_t item_size, size_t count) : + p_start(static_cast(mem)), + p_end(p_start + (item_size * count)), + item_size(item_size) { + assert((p_end - p_start) / item_size == count); + } + + public: + class iterator { + public: + using difference_type = std::ptrdiff_t; + using value_type = std::byte; + private: + std::byte *pos; + size_t size; + + public: + iterator() : pos(nullptr), size(0) {}; + iterator(std::byte *p, size_t item_size) : pos(p), size(item_size) {}; + + void Move(size_t n) { + pos += (n * size); + } + + iterator &operator++() { + Move(1); + return *this; + } + iterator operator++(int) { + iterator tmp(*this); + operator++(); + return tmp; + } + + iterator &operator--() { + Move(-1); + return *this; + } + iterator operator--(int) { + iterator tmp(*this); + operator--(); + return tmp; + } + + iterator &operator+=(difference_type n) { + Move(n); + return *this; + } + iterator &operator-=(difference_type n) { + Move(-n); + return *this; + } + + iterator operator+(const difference_type &n) const { + iterator tmp(*this); + tmp.Move(n); + return tmp; + } + iterator operator-(const difference_type &n) const { + iterator tmp(*this); + tmp.Move(-n); + return tmp; + } + + std::byte &operator[](const difference_type &n) const { + iterator tmp(*this); + tmp.Move(n); + return *tmp; + } + + difference_type operator-(const iterator &rhs) const { return pos - rhs.pos; } + + bool operator==(const iterator &rhs) const { return pos == rhs.pos; } + bool operator!=(const iterator &rhs) const { return pos != rhs.pos; } + + bool operator<(const iterator &rhs) const { return pos < rhs.pos; } + bool operator<=(const iterator &rhs) const { return pos <= rhs.pos; } + bool operator>(const iterator &rhs) const { return pos > rhs.pos; } + bool operator>=(const iterator &rhs) const { return pos >= rhs.pos; } + + std::byte &operator*() const { return *pos; } + + template + T &as() { return *(T *) pos; } + template + T *as_ptr() { return (T *) pos; } + + [[nodiscard]] void *ptr() const { return pos; } + }; + + [[nodiscard]] iterator begin() const { + return {p_start, item_size}; + } + + iterator end() const { + return {p_end, item_size}; + } + + iterator last() const { + return end()--; + } + }; + + span_dynamic::iterator operator+(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { + span_dynamic::iterator tmp(i); + tmp.Move(n); + return tmp; + } + span_dynamic::iterator operator-(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { + span_dynamic::iterator tmp(i); + tmp.Move(-n); + return tmp; + } + + bool operator<(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() < rhs; } + bool operator<(const void *lhr, const span_dynamic::iterator &rhs) { return lhr < rhs.ptr(); } + bool operator<=(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() <= rhs; } + bool operator>(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() > rhs; } + bool operator>(const void *lhr, const span_dynamic::iterator &rhs) { return lhr > rhs.ptr(); } + bool operator>=(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() >= rhs; } + bool operator>=(const void *lhr, const span_dynamic::iterator &rhs) { return lhr >= rhs.ptr(); } +} + +static_assert(std::input_or_output_iterator); +static_assert(std::random_access_iterator); + +static_assert(std::ranges::random_access_range); + +namespace SH { + + class PoolAllocator { + public: + + struct Item { + Item *next; + }; + + explicit PoolAllocator(size_t item_size) : item_size(std::max(item_size, sizeof(Item))) { + chunks.push_back(new Chunk(item_size)); + } + + class Chunk { + span_dynamic memory; //= span_dynamic(nullptr, 0, 0); + Item *next_free = nullptr; + public: + explicit Chunk(size_t item_size) { + void *m = malloc(item_size * 1024); + memory = span_dynamic(m, item_size, 1024); + + next_free = memory.begin().as_ptr(); + for (auto it = memory.begin(); it != memory.end(); it++) { + auto next = it + 1; + it.as_ptr()->next = next.as_ptr(); + } + memory.last().as_ptr()->next = nullptr; + } + + bool HasSpace() { return next_free != nullptr; } + + void *allocate() { + assert(next_free != nullptr); + auto ptr = next_free; + next_free = next_free->next; + return ptr; + } + + void deallocate(void *p) { + assert(contains(p)); + auto item = static_cast(p); + item->next = next_free; + next_free = item; + } + + bool contains(void *p) { + return p >= memory.begin() && p < memory.end(); + } + }; + + public: + + virtual void *allocate() { + auto chunk_it = std::ranges::find_if(chunks, [](auto chunk) { return chunk->HasSpace(); }); + + // If no chunk has space, create a new one + if (chunk_it == chunks.end()) { + chunks.push_back(new Chunk(item_size)); + chunk_it = std::prev(chunks.end()); + } + + return (*chunk_it)->allocate(); + }; + + virtual void deallocate(void *p) { + auto chunk_it = std::ranges::find_if(chunks, [p](auto chunk) { return chunk->contains(p); }); + if (chunk_it == chunks.end()) + throw std::invalid_argument("WTF man common use pointer correctly"); + + (*chunk_it)->deallocate(p); + }; + private: + std::vector chunks; + size_t item_size; + }; + +} + +/* + * Archetype : (T1, T2, T3, T4) + * | self (T1) | comp 1 (T2) | comp 2 (T3) | comp 3 (T4) | + * | T1: 1 | T2: 1 | T3: 1 | T4: 1 | + * | T1: 2 | T2: 2 | T3: 2 | T4: 2 | + * | T1: 3 | T2: 3 | T3: 3 | T4: 3 | + * + * Archetype : (T1, T2, T3) + * | self (T1) | comp 1 (T2) | comp 2 (T3) | + * | T1: 4 | T2: 4 | T3: 4 | + * | T1: 5 | T2: 5 | T3: 5 | + * + * + */ + +//#################################################### +//################## ID system ####################### +//#################################################### +#pragma region ID-System + +union Id { + std::byte bytes[sizeof(uint64_t)]; + uint32_t half[2]; + uint64_t id; + + Id(uint64_t id) : id(id) {}; + Id(uint32_t high, uint32_t low) : half{high, low} {}; + + Id Next() { + return Id(this->id++); + } +}; + +using TypeId = Id; + +template<> +struct std::hash { + std::size_t operator()(const TypeId &s) const noexcept { + return std::hash{}(s.id); + }; +}; + +bool operator==(const Id &lhs, const Id &rhs) { return lhs.id == rhs.id; } + +std::unordered_map typeMap; + +TypeId next_id(0); + +template +TypeId GetTypeId() { + static TypeId id = next_id.id++; + static auto a = typeMap.insert({id, typeid(T).name()}); + return id; +} + +#pragma endregion ID-System + +//#################################################### +//################## Entity base classes ############# +//#################################################### + +class Object { + +}; + +class Component : public Object { +public: + static constexpr bool isEntity = false; + Id UUID; + + Component(Id UUID) : UUID(UUID) {}; +}; +template +concept component = std::is_base_of_v; + +template +concept component_only = component && T::isEntity == false; + +class Entity : public Component { +public: + static constexpr bool isEntity = true; + Entity(Id UUID) : Component(UUID) { + + } + + template + T *AddInternalChild(); + +}; +template +concept entity = std::is_base_of_v && T::isEntity == true; + +//#################################################### +//############### Prefab stuff ####################### +//#################################################### + +class Asset { + +}; + +class Prefab : public Asset { + +}; + +class PrefabEntity : public Entity { + Prefab p; +public: + PrefabEntity(Id UUID, Prefab asset) : Entity(UUID), p(asset) { + + } + +}; + +//#################################################### +//################Built in entities ################## +//#################################################### + +class Scene : public Entity { + std::string name = "Test"; +public: + Scene(Id UUID) : Entity(UUID) {}; +}; + +enum Relation { + PARENT = 1, +}; + +class Archetype { +public: + using Id = uint32_t; + static Id next_id; + + using Types = std::vector; + + Types types; + Id id; + + using Column = std::vector; + + std::vector data = std::vector(0); + + static Types sortTypes(Types t) { + std::ranges::sort(t, [](auto a, auto b) { return a.id < b.id; }); + return t; + } +public: + Archetype() = default; + Archetype(std::initializer_list types) : types(sortTypes(types)), id(next_id++) {} + Archetype(Types types) : types(sortTypes(types)), id(next_id++) {} + + std::pair AddRow() { + auto &row = data.emplace_back(Column(types.size())); + return {data.size() - 1, row}; + } + + size_t getColumn(TypeId column_type) { + auto a = std::ranges::find(types, column_type); + if(a == types.end()) + return -1; + + size_t const pos = std::distance(types.begin(), a); + return pos; + } + + void RemoveRow(size_t i) { + data.erase(data.begin() + i); + }; +}; + +template<> +struct std::hash { + std::size_t operator()(const Archetype::Types &vec) const noexcept { + std::size_t seed = vec.size(); + for (auto &i : vec) { + seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } +}; + +class EntityManager { + + std::unordered_map pools; + + template + SH::PoolAllocator &GetPool(TypeId type) { + if (!pools.contains(type)) { + pools.emplace(type, SH::PoolAllocator(sizeof(Ent))); + } + return pools.at(type); + } + + std::unordered_map archetypes; + + struct ArchetypeRecord { + Archetype *archetype; + size_t row; + }; + + std::unordered_map entity_archetype; + + Archetype &GetArchetype(Archetype arc) { + if (archetypes.contains(arc.types)) { + return archetypes.at(arc.types); + } else { + auto res = archetypes.emplace(arc.types, arc).first; + return res->second; + } + } + + ArchetypeRecord &GetEntityArchetype(Entity &ent) { + return entity_archetype.at(ent.UUID); + } + + uint32_t nextUUID = 0; + Id GetNewUUID() { + return Id(nextUUID++, 0); + } + + template + void *AllocateForNew() { + SH::PoolAllocator &pool = GetPool(GetTypeId()); + void *pos = pool.allocate(); + return pos; + } + + void MoveToArchetype(ArchetypeRecord &src, Archetype &dst){ + auto dst_row = dst.AddRow(); + + for (size_t i = 0; i < src.archetype->types.size(); i++) { + auto column_type = src.archetype->types[i]; + size_t dest_column = dst.getColumn(column_type); + if(dest_column == -1) + continue; + dst_row.second[dest_column] = src.archetype->data[src.row][i]; + } + + src.archetype->RemoveRow(src.row); + + src.row = dst_row.first; + src.archetype = &dst; + } + +public: + static EntityManager *entity_manager; + + EntityManager() { + entity_manager = this; + } + + template + Comp *AddChild(Entity &parent) { + // Allocate the space for the entity + const TypeId &comp_id = GetTypeId(); + + void *memory = AllocateForNew(); + + //Find old archetype + auto &record = GetEntityArchetype(parent); + auto &old_arch = *record.archetype; + + //Construct new type list and find the archetype + Archetype::Types types(old_arch.types); + types.emplace_back(comp_id); + + Archetype &new_arch = GetArchetype(Archetype(types)); + + MoveToArchetype(record, new_arch); + + record.archetype->data[record.row][new_arch.getColumn(comp_id)] = (Comp *) memory; + // Create it + Comp *component = new(memory)Comp(GetNewUUID()); + + // Add it to the parent + return component; + } + + template + Ent *AddChild(Entity &parent) { + const TypeId &type_id = GetTypeId(); + + // Allocate the space for the entity + void *pos = AllocateForNew(); + + //Find or make archetype + Archetype &a = GetArchetype( + Archetype({ + type_id, + Id(parent.UUID.half[0], Relation::PARENT) + })); + + auto &row = a.AddRow().second; + row[a.getColumn(type_id)] = (Ent *) pos; + + Id Uuid = GetNewUUID(); + + entity_archetype.insert({Uuid, ArchetypeRecord{&a, a.getColumn(type_id)}}); + + // Create it + Ent *entity = new(pos)Ent(Uuid); + + return entity; + } + + /* + * Add a fresh new entity + */ + template + Ent *Add() { + // Allocate the space for the entity + void *pos = AllocateForNew(); + + //Find or make archetype + Archetype &a = GetArchetype(Archetype({GetTypeId()})); + + auto &row = a.AddRow().second; + row[0] = (Ent *) pos; + + Id Uuid = GetNewUUID(); + + entity_archetype.insert({Uuid, ArchetypeRecord{&a, a.getColumn(GetTypeId())}}); + + // Create it + Ent *entity = new(pos)Ent(Uuid); + + return entity; + } + + #pragma region DumpData + std::string GetTypeOrRelationName(Id type_id) { + std::string res = ""; + if (type_id.half[1] > 0) { + switch (type_id.half[1]) { + case Relation::PARENT:res += "Parent: "; + break; + default:break; + } + } + res += typeMap.at(type_id.half[0]); + return res; + } + + void DumpData() { + + { + std::printf("Pools: \n"); + for (auto &pool : pools) { + fmt::print("{0:20} count:{1}\n", typeMap.at(pool.first), "??"); + } + std::printf("\n"); + } + + { + std::printf("Archetypes: \n"); + size_t max_len = 0; + for (auto &arch : archetypes) { + std::vector target(arch.second.types.size()); + for (int i = 0; i < arch.second.types.size(); ++i) { + target[i] = GetTypeOrRelationName(arch.second.types[i]); + } + max_len = std::max(max_len, fmt::format("{}", fmt::join(target, " | ")).size()); + } + + for (auto &arch : archetypes) { + std::printf("%i :( ", arch.second.id); + + std::vector target(arch.second.types.size()); + for (int i = 0; i < arch.second.types.size(); ++i) { + target[i] = GetTypeOrRelationName(arch.second.types[i]); + } + + fmt::print("{0:<{1}} )\t\t", fmt::format("{}", fmt::join(target, " | ")), max_len); + fmt::print("count: {}", arch.second.data.size()); + + fmt::print("\n"); + } + fmt::print("\n"); + } + + std::printf("Entity map: \n"); + for (auto &data : entity_archetype) { + + fmt::print("ID: {0}, (row: {1}, arch: {2})", data.first.id, data.second.row, data.second.archetype->id); + + fmt::println(""); + } + } + #pragma endregion DumpData + +}; + +template +T *Entity::AddInternalChild() { + return EntityManager::entity_manager->AddChild(*this); +} + +EntityManager *EntityManager::entity_manager = nullptr; + +Archetype::Id Archetype::next_id = 0; + + diff --git a/projs/experiments/ecs/src/ecs.exp.cpp b/projs/experiments/ecs/src/ecs.exp.cpp new file mode 100644 index 00000000..42d2daae --- /dev/null +++ b/projs/experiments/ecs/src/ecs.exp.cpp @@ -0,0 +1,69 @@ +// +// Created by dpeter99 on 22/11/24. +// +#include "ecs.exp.h" + +#include + +Archetype::Id Archetype::next_id = 0; + +Types sortTypes(Types t) +{ + std::ranges::sort(t, [](auto a, auto b) { return a.id < b.id; }); + return t; +} + +Archetype::Archetype(const Types& types_list): id(next_id++) +{ + size_t next_c = 0; + const auto sortedTypes = sortTypes(types_list); + std::ranges::for_each(sortedTypes, [&](const TypeId& type) + { + if(test(type.flags, TypeFlags::Flag)) + { + this->types.insert({type, -1}); + } + else + { + this->types.insert({type, next_c++}); + TypeInfo into = GetTypeInfoById(type); + void* page = malloc(into.size * 1024); + this->columns.push_back({page, into.size, 1024}); + } + }); +} + +void PrintArchetype(const Archetype& a) +{ + std::cout << "Architype ID: " << a.id << std::endl; + std::cout << "Types:" << std::endl; + for (const auto& [type, column] : a.types) + { + std::cout << "\t" + << "Type: " << GetTypeNameByID(type) << " (" << type.id << ")" + <<" Column: " << column; + if(column >= 0) + { + std::cout << " Page: " << a.columns[column].begin().ptr(); + } + + + + std::cout << std::endl; + } + +} + + + +Archetype& EntityManager::GetArchetype(const Types& types) +{ + if(archetypes.contains(types)) + { + return archetypes.at(types); + } + + Archetype a(types); + const auto res = archetypes.emplace(types, Archetype{types}); + return res.first->second; +} diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 2ddbcb0a..7a4d3aee 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -9,228 +9,11 @@ #include #include -#include +#include "span_dynamic.h" -//#include "../../shadow/shadow-engine/reflection/inc/shadow/SHObject.h" -//#include "../../shadow/shadow-engine/core/inc/shadow/exports.h" - - -namespace SH { - - class span_dynamic { - std::byte *p_start; - std::byte *p_end; - size_t item_size; - public: - span_dynamic() = default; - span_dynamic(void *mem, size_t item_size, size_t count) : - p_start(static_cast(mem)), - p_end(p_start + (item_size * count)), - item_size(item_size) { - assert((p_end - p_start) / item_size == count); - } - - public: - class iterator { - public: - using difference_type = std::ptrdiff_t; - using value_type = std::byte; - private: - std::byte *pos; - size_t size; - - public: - iterator() : pos(nullptr), size(0) {}; - iterator(std::byte *p, size_t item_size) : pos(p), size(item_size) {}; - - void Move(size_t n) { - pos += (n * size); - } - - iterator &operator++() { - Move(1); - return *this; - } - iterator operator++(int) { - iterator tmp(*this); - operator++(); - return tmp; - } - - iterator &operator--() { - Move(-1); - return *this; - } - iterator operator--(int) { - iterator tmp(*this); - operator--(); - return tmp; - } - - iterator &operator+=(difference_type n) { - Move(n); - return *this; - } - iterator &operator-=(difference_type n) { - Move(-n); - return *this; - } - - iterator operator+(const difference_type &n) const { - iterator tmp(*this); - tmp.Move(n); - return tmp; - } - iterator operator-(const difference_type &n) const { - iterator tmp(*this); - tmp.Move(-n); - return tmp; - } - - std::byte &operator[](const difference_type &n) const { - iterator tmp(*this); - tmp.Move(n); - return *tmp; - } - - difference_type operator-(const iterator &rhs) const { return pos - rhs.pos; } - - bool operator==(const iterator &rhs) const { return pos == rhs.pos; } - bool operator!=(const iterator &rhs) const { return pos != rhs.pos; } - - bool operator<(const iterator &rhs) const { return pos < rhs.pos; } - bool operator<=(const iterator &rhs) const { return pos <= rhs.pos; } - bool operator>(const iterator &rhs) const { return pos > rhs.pos; } - bool operator>=(const iterator &rhs) const { return pos >= rhs.pos; } - - std::byte &operator*() const { return *pos; } - - template - T &as() { return *(T *) pos; } - template - T *as_ptr() { return (T *) pos; } - - [[nodiscard]] void *ptr() const { return pos; } - }; - - [[nodiscard]] iterator begin() const { - return {p_start, item_size}; - } - - iterator end() const { - return {p_end, item_size}; - } - - iterator last() const { - return end()--; - } - }; - - span_dynamic::iterator operator+(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { - span_dynamic::iterator tmp(i); - tmp.Move(n); - return tmp; - } - span_dynamic::iterator operator-(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) { - span_dynamic::iterator tmp(i); - tmp.Move(-n); - return tmp; - } - - bool operator<(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() < rhs; } - bool operator<(const void *lhr, const span_dynamic::iterator &rhs) { return lhr < rhs.ptr(); } - bool operator<=(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() <= rhs; } - bool operator>(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() > rhs; } - bool operator>(const void *lhr, const span_dynamic::iterator &rhs) { return lhr > rhs.ptr(); } - bool operator>=(const span_dynamic::iterator &lhr, const void *rhs) { return lhr.ptr() >= rhs; } - bool operator>=(const void *lhr, const span_dynamic::iterator &rhs) { return lhr >= rhs.ptr(); } -} - -static_assert(std::input_or_output_iterator); -static_assert(std::random_access_iterator); - -static_assert(std::ranges::random_access_range); - -namespace SH { - - class PoolAllocator { - public: - - struct Item { - Item *next; - }; - - explicit PoolAllocator(size_t item_size) : item_size(std::max(item_size, sizeof(Item))) { - chunks.push_back(new Chunk(item_size)); - } - - class Chunk { - span_dynamic memory; //= span_dynamic(nullptr, 0, 0); - Item *next_free = nullptr; - public: - explicit Chunk(size_t item_size) { - void *m = malloc(item_size * 1024); - memory = span_dynamic(m, item_size, 1024); - - next_free = memory.begin().as_ptr(); - for (auto it = memory.begin(); it != memory.end(); it++) { - auto next = it + 1; - it.as_ptr()->next = next.as_ptr(); - } - memory.last().as_ptr()->next = nullptr; - } - - bool HasSpace() { return next_free != nullptr; } - - void *allocate() { - assert(next_free != nullptr); - auto ptr = next_free; - next_free = next_free->next; - return ptr; - } - - void deallocate(void *p) { - assert(contains(p)); - auto item = static_cast(p); - item->next = next_free; - next_free = item; - } - - bool contains(void *p) { - return p >= memory.begin() && p < memory.end(); - } - }; - - public: - - virtual void *allocate() { - auto chunk_it = std::ranges::find_if(chunks, [](auto chunk) { return chunk->HasSpace(); }); - - // If no chunk has space, create a new one - if (chunk_it == chunks.end()) { - chunks.push_back(new Chunk(item_size)); - chunk_it = std::prev(chunks.end()); - } - - return (*chunk_it)->allocate(); - }; - - virtual void deallocate(void *p) { - auto chunk_it = std::ranges::find_if(chunks, [p](auto chunk) { return chunk->contains(p); }); - if (chunk_it == chunks.end()) - throw std::invalid_argument("WTF man common use pointer correctly"); - - (*chunk_it)->deallocate(p); - }; - private: - std::vector chunks; - size_t item_size; - }; - -} /* - * Archetype : (T1, T2, T3, T4) + * Archetype : T1 (T2, T3, T4) * | self (T1) | comp 1 (T2) | comp 2 (T3) | comp 3 (T4) | * | T1: 1 | T2: 1 | T3: 1 | T4: 1 | * | T1: 2 | T2: 2 | T3: 2 | T4: 2 | @@ -247,386 +30,72 @@ namespace SH { //#################################################### //################## ID system ####################### //#################################################### -#pragma region ID-System - -union Id { - std::byte bytes[sizeof(uint64_t)]; - uint32_t half[2]; - uint64_t id; - - Id(uint64_t id) : id(id) {}; - Id(uint32_t high, uint32_t low) : half{high, low} {}; +#include - Id Next() { - return Id(this->id++); - } -}; - -using TypeId = Id; - -template<> -struct std::hash { - std::size_t operator()(const TypeId &s) const noexcept { - return std::hash{}(s.id); - }; -}; - -bool operator==(const Id &lhs, const Id &rhs) { return lhs.id == rhs.id; } - -std::unordered_map typeMap; - -TypeId next_id(0); - -template -TypeId GetTypeId() { - static TypeId id = next_id.id++; - static auto a = typeMap.insert({id, typeid(T).name()}); - return id; -} - -#pragma endregion ID-System +#include "id_system.h" //#################################################### //################## Entity base classes ############# //#################################################### -class Object { -}; - -class Component : public Object { -public: - static constexpr bool isEntity = false; - Id UUID; - - Component(Id UUID) : UUID(UUID) {}; -}; -template -concept component = std::is_base_of_v; - -template -concept component_only = component && T::isEntity == false; - -class Entity : public Component { -public: - static constexpr bool isEntity = true; - Entity(Id UUID) : Component(UUID) { - - } - - template - T *AddInternalChild(); - -}; -template -concept entity = std::is_base_of_v && T::isEntity == true; //#################################################### -//############### Prefab stuff ####################### +//################### Archetype ###################### //#################################################### -class Asset { - -}; - -class Prefab : public Asset { +using Types = std::vector; -}; +Types sortTypes(Types t); -class PrefabEntity : public Entity { - Prefab p; -public: - PrefabEntity(Id UUID, Prefab asset) : Entity(UUID), p(asset) { - - } - -}; - -//#################################################### -//################Built in entities ################## -//#################################################### - -class Scene : public Entity { - std::string name = "Test"; -public: - Scene(Id UUID) : Entity(UUID) {}; -}; - -enum Relation { - PARENT = 1, -}; - -class Archetype { +class Archetype +{ public: using Id = uint32_t; static Id next_id; - using Types = std::vector; + using Columns = std::map; + - Types types; Id id; + Columns types; - using Column = std::vector; + std::vector columns; - std::vector data = std::vector(0); - static Types sortTypes(Types t) { - std::ranges::sort(t, [](auto a, auto b) { return a.id < b.id; }); - return t; - } -public: Archetype() = default; - Archetype(std::initializer_list types) : types(sortTypes(types)), id(next_id++) {} - Archetype(Types types) : types(sortTypes(types)), id(next_id++) {} - - std::pair AddRow() { - auto &row = data.emplace_back(Column(types.size())); - return {data.size() - 1, row}; - } - - size_t getColumn(TypeId column_type) { - auto a = std::ranges::find(types, column_type); - if(a == types.end()) - return -1; - size_t const pos = std::distance(types.begin(), a); - return pos; - } + Archetype(const std::initializer_list types_list) : Archetype(std::vector(types_list)) {} - void RemoveRow(size_t i) { - data.erase(data.begin() + i); - }; + explicit Archetype(const Types& types_list); }; -template<> -struct std::hash { - std::size_t operator()(const Archetype::Types &vec) const noexcept { - std::size_t seed = vec.size(); - for (auto &i : vec) { - seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } - return seed; - } -}; +void PrintArchetype(const Archetype& a); -class EntityManager { - std::unordered_map pools; - template - SH::PoolAllocator &GetPool(TypeId type) { - if (!pools.contains(type)) { - pools.emplace(type, SH::PoolAllocator(sizeof(Ent))); - } - return pools.at(type); - } - - std::unordered_map archetypes; - - struct ArchetypeRecord { - Archetype *archetype; - size_t row; - }; - - std::unordered_map entity_archetype; - - Archetype &GetArchetype(Archetype arc) { - if (archetypes.contains(arc.types)) { - return archetypes.at(arc.types); - } else { - auto res = archetypes.emplace(arc.types, arc).first; - return res->second; - } - } - - ArchetypeRecord &GetEntityArchetype(Entity &ent) { - return entity_archetype.at(ent.UUID); - } - - uint32_t nextUUID = 0; - Id GetNewUUID() { - return Id(nextUUID++, 0); - } - - template - void *AllocateForNew() { - SH::PoolAllocator &pool = GetPool(GetTypeId()); - void *pos = pool.allocate(); - return pos; - } - - void MoveToArchetype(ArchetypeRecord &src, Archetype &dst){ - auto dst_row = dst.AddRow(); - - for (size_t i = 0; i < src.archetype->types.size(); i++) { - auto column_type = src.archetype->types[i]; - size_t dest_column = dst.getColumn(column_type); - if(dest_column == -1) - continue; - dst_row.second[dest_column] = src.archetype->data[src.row][i]; - } - - src.archetype->RemoveRow(src.row); - - src.row = dst_row.first; - src.archetype = &dst; +template<> +struct std::hash { + std::size_t operator()(const Types &vec) const noexcept { + std::size_t seed = vec.size(); + for (auto &i : vec) { + seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; } +}; +class EntityManager +{ public: - static EntityManager *entity_manager; - - EntityManager() { - entity_manager = this; - } - - template - Comp *AddChild(Entity &parent) { - // Allocate the space for the entity - const TypeId &comp_id = GetTypeId(); - - void *memory = AllocateForNew(); - - //Find old archetype - auto &record = GetEntityArchetype(parent); - auto &old_arch = *record.archetype; - - //Construct new type list and find the archetype - Archetype::Types types(old_arch.types); - types.emplace_back(comp_id); - - Archetype &new_arch = GetArchetype(Archetype(types)); - - MoveToArchetype(record, new_arch); - - record.archetype->data[record.row][new_arch.getColumn(comp_id)] = (Comp *) memory; - // Create it - Comp *component = new(memory)Comp(GetNewUUID()); - - // Add it to the parent - return component; - } - - template - Ent *AddChild(Entity &parent) { - const TypeId &type_id = GetTypeId(); - - // Allocate the space for the entity - void *pos = AllocateForNew(); - - //Find or make archetype - Archetype &a = GetArchetype( - Archetype({ - type_id, - Id(parent.UUID.half[0], Relation::PARENT) - })); - - auto &row = a.AddRow().second; - row[a.getColumn(type_id)] = (Ent *) pos; - - Id Uuid = GetNewUUID(); - - entity_archetype.insert({Uuid, ArchetypeRecord{&a, a.getColumn(type_id)}}); - - // Create it - Ent *entity = new(pos)Ent(Uuid); - - return entity; - } - - /* - * Add a fresh new entity - */ - template - Ent *Add() { - // Allocate the space for the entity - void *pos = AllocateForNew(); - - //Find or make archetype - Archetype &a = GetArchetype(Archetype({GetTypeId()})); - - auto &row = a.AddRow().second; - row[0] = (Ent *) pos; - - Id Uuid = GetNewUUID(); - - entity_archetype.insert({Uuid, ArchetypeRecord{&a, a.getColumn(GetTypeId())}}); - - // Create it - Ent *entity = new(pos)Ent(Uuid); - - return entity; - } - - #pragma region DumpData - std::string GetTypeOrRelationName(Id type_id) { - std::string res = ""; - if (type_id.half[1] > 0) { - switch (type_id.half[1]) { - case Relation::PARENT:res += "Parent: "; - break; - default:break; - } - } - res += typeMap.at(type_id.half[0]); - return res; - } - - void DumpData() { - - { - std::printf("Pools: \n"); - for (auto &pool : pools) { - fmt::print("{0:20} count:{1}\n", typeMap.at(pool.first), "??"); - } - std::printf("\n"); - } - - { - std::printf("Archetypes: \n"); - size_t max_len = 0; - for (auto &arch : archetypes) { - std::vector target(arch.second.types.size()); - for (int i = 0; i < arch.second.types.size(); ++i) { - target[i] = GetTypeOrRelationName(arch.second.types[i]); - } - max_len = std::max(max_len, fmt::format("{}", fmt::join(target, " | ")).size()); - } - - for (auto &arch : archetypes) { - std::printf("%i :( ", arch.second.id); - - std::vector target(arch.second.types.size()); - for (int i = 0; i < arch.second.types.size(); ++i) { - target[i] = GetTypeOrRelationName(arch.second.types[i]); - } - - fmt::print("{0:<{1}} )\t\t", fmt::format("{}", fmt::join(target, " | ")), max_len); - fmt::print("count: {}", arch.second.data.size()); - - fmt::print("\n"); - } - fmt::print("\n"); - } - - std::printf("Entity map: \n"); - for (auto &data : entity_archetype) { - - fmt::print("ID: {0}, (row: {1}, arch: {2})", data.first.id, data.second.row, data.second.archetype->id); - - fmt::println(""); - } - } - #pragma endregion DumpData - -}; + std::unordered_map archetypes; -template -T *Entity::AddInternalChild() { - return EntityManager::entity_manager->AddChild(*this); -} + EntityManager(): archetypes() + { + } ; -EntityManager *EntityManager::entity_manager = nullptr; + Archetype& GetArchetype(const Types& types); -Archetype::Id Archetype::next_id = 0; +}; \ No newline at end of file diff --git a/projs/experiments/ecs/src/entity.h b/projs/experiments/ecs/src/entity.h new file mode 100644 index 00000000..94cdff2e --- /dev/null +++ b/projs/experiments/ecs/src/entity.h @@ -0,0 +1,39 @@ + +#pragma once + +class Object +{ +}; + +class Component : public Object +{ +public: + static constexpr bool isEntity = false; + Id UUID; + + Component(Id UUID) : UUID(UUID) + { + }; +}; + +template +concept component = std::is_base_of_v; + +template +concept component_only = component && T::isEntity == false; + +class Entity : public Component +{ +public: + static constexpr bool isEntity = true; + + Entity(Id UUID) : Component(UUID) + { + } + + template + T* AddInternalChild(); +}; + +template +concept entity = std::is_base_of_v && T::isEntity == true; \ No newline at end of file diff --git a/projs/experiments/ecs/src/id_system.cpp b/projs/experiments/ecs/src/id_system.cpp new file mode 100644 index 00000000..2691218b --- /dev/null +++ b/projs/experiments/ecs/src/id_system.cpp @@ -0,0 +1,31 @@ +#include "id_system.h" + +std::unordered_map typeMap; + +TypeId next_id({.id = 1}); + +std::size_t std::hash::operator()(const TypeId& s) const noexcept +{ + return std::hash{}(s.id); +} + +TypeInfo GetTypeInfoById(const TypeId& id) +{ + return std::ranges::find_if(typeMap, [id](const auto& i) + { + return i.second.id == id; + })->second; +} + +std::string GetTypeNameByID(TypeId id) +{ + return std::ranges::find_if(typeMap, [id](const auto& i) + { + return i.second.id == id; + })->first; +} + +void __jumpTypeID(uint32_t n) +{ + next_id.id += n; +} diff --git a/projs/experiments/ecs/src/id_system.h b/projs/experiments/ecs/src/id_system.h new file mode 100644 index 00000000..cf1312e1 --- /dev/null +++ b/projs/experiments/ecs/src/id_system.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class TypeFlags : std::uint8_t { + None = 0, + Flag = 1 << 1, + Relation = 1 << 2, + Unused = 1 << 3, + Unused2 = 1 << 4, +}; + +inline TypeFlags operator|(TypeFlags lhs, TypeFlags rhs) { + return static_cast( + static_cast>(lhs) | + static_cast>(rhs) + ); +} +inline TypeFlags operator&(const TypeFlags& lhs, const TypeFlags& rhs) +{ + return static_cast( + static_cast>(lhs) & + static_cast>(rhs) + ); +} +inline bool test(const TypeFlags& lhs, const TypeFlags& rhs) +{ + return static_cast>(lhs & rhs); +} + + + + +struct __attribute__((packed)) TypeId +{ + uint32_t id : 28; + TypeFlags flags : 4; + uint32_t entity; +}; +static_assert(sizeof(TypeId) == sizeof(uint64_t)); + +inline int operator<(const TypeId& lhs, const TypeId& rhs){ return lhs.id - rhs.id; } +inline bool operator==(const TypeId& lhs, const TypeId& rhs) { return lhs.id == rhs.id; } + +template <> +struct std::hash +{ + std::size_t operator()(const TypeId& s) const noexcept;; +}; + + + +struct TypeInfo +{ + TypeId id; + std::string name; + size_t size; +}; + + + + +extern std::unordered_map typeMap; + +extern TypeId next_id; + +template +TypeInfo GetTypeId(const TypeFlags& flags = TypeFlags::None) +{ + const char* name = typeid(T).name(); + if(!typeMap.contains(name)) + { + TypeInfo newId = { + .id = {.id = next_id.id++, .flags = flags}, + .name = name, + .size = sizeof(T), + }; + typeMap.emplace(name, newId); + return newId; + } + return typeMap.at(name); +} + +TypeInfo GetTypeInfoById(const TypeId& id); + +std::string GetTypeNameByID(TypeId id); + +void __jumpTypeID(uint32_t n); diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp index 07ff22a3..1dca0a88 100644 --- a/projs/experiments/ecs/src/main.cpp +++ b/projs/experiments/ecs/src/main.cpp @@ -3,42 +3,21 @@ // #include "ecs.exp.h" -class Position : public Component { -public: - float x; - float y; - float z; -}; +struct A {}; +struct B {}; +struct C {}; -class Mesh : public Component { - -}; - -class HP : public Component { -public: - int Hp; -}; - -class Player : public Entity { -public: - Player(Id UUID) : Entity(UUID) { - auto *p = this->AddInternalChild(); - p->x = 10; - this->AddInternalChild(); - this->AddInternalChild(); - } - -}; - -static_assert(Scene::isEntity == true); int main() { - EntityManager em; - Scene *scene = em.Add(); - Player *player = em.AddChild(*scene); + EntityManager em; + auto& a = em.GetArchetype({ + GetTypeId().id, + GetTypeId(TypeFlags::Flag).id, + GetTypeId().id, + }); - em.DumpData(); + PrintArchetype(a); return 0; -} \ No newline at end of file +} diff --git a/projs/experiments/ecs/src/span_dynamic.cpp b/projs/experiments/ecs/src/span_dynamic.cpp new file mode 100644 index 00000000..0792d7fe --- /dev/null +++ b/projs/experiments/ecs/src/span_dynamic.cpp @@ -0,0 +1 @@ +#include "span_dynamic.h" diff --git a/projs/experiments/ecs/src/span_dynamic.h b/projs/experiments/ecs/src/span_dynamic.h new file mode 100644 index 00000000..7b03b577 --- /dev/null +++ b/projs/experiments/ecs/src/span_dynamic.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SH +{ + /** + * A view of a given memory area in item_size steps + */ + class span_dynamic + { + std::byte* p_start; + std::byte* p_end; + size_t item_size; + + public: + span_dynamic() = default; + + span_dynamic(void* mem, const size_t item_size, const size_t count) : + p_start(static_cast(mem)), + p_end(p_start + (item_size * count)), + item_size(item_size) + { + assert((p_end - p_start) / item_size == count); + } + + class iterator + { + public: + using difference_type = std::ptrdiff_t; + using value_type = std::byte; + + private: + std::byte* pos; + size_t size; + + public: + iterator() : pos(nullptr), size(0) + { + }; + + iterator(std::byte* p, const size_t item_size) : pos(p), size(item_size) + { + }; + + void Move(size_t n) + { + pos += (n * size); + } + + iterator& operator++() + { + Move(1); + return *this; + } + + iterator operator++(int) + { + const iterator tmp(*this); + operator++(); + return tmp; + } + + iterator& operator--() + { + Move(-1); + return *this; + } + + iterator operator--(int) + { + const iterator tmp(*this); + operator--(); + return tmp; + } + + iterator& operator+=(const difference_type n) + { + Move(n); + return *this; + } + + iterator& operator-=(const difference_type n) + { + Move(-n); + return *this; + } + + iterator operator+(const difference_type& n) const + { + iterator tmp(*this); + tmp.Move(n); + return tmp; + } + + iterator operator-(const difference_type& n) const + { + iterator tmp(*this); + tmp.Move(-n); + return tmp; + } + + std::byte& operator[](const difference_type& n) const + { + iterator tmp(*this); + tmp.Move(n); + return *tmp; + } + + difference_type operator-(const iterator& rhs) const { return pos - rhs.pos; } + + bool operator==(const iterator& rhs) const { return pos == rhs.pos; } + bool operator!=(const iterator& rhs) const { return pos != rhs.pos; } + + bool operator<(const iterator& rhs) const { return pos < rhs.pos; } + bool operator<=(const iterator& rhs) const { return pos <= rhs.pos; } + bool operator>(const iterator& rhs) const { return pos > rhs.pos; } + bool operator>=(const iterator& rhs) const { return pos >= rhs.pos; } + + std::byte& operator*() const { return *pos; } + + template + T &as() { return *(T *) pos; } + template + T *as_ptr() { return (T *) pos; } + + [[nodiscard]] void* ptr() const { return pos; } + }; + + [[nodiscard]] iterator begin() const + { + return {p_start, item_size}; + } + + iterator end() const + { + return {p_end, item_size}; + } + + iterator last() const + { + return end()--; + } + }; + + inline span_dynamic::iterator operator+(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) + { + span_dynamic::iterator tmp(i); + tmp.Move(n); + return tmp; + } + + inline span_dynamic::iterator operator-(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) + { + span_dynamic::iterator tmp(i); + tmp.Move(-n); + return tmp; + } + + inline bool operator<(const span_dynamic::iterator& lhr, const void* rhs) { return lhr.ptr() < rhs; } + inline bool operator<(const void* lhr, const span_dynamic::iterator& rhs) { return lhr < rhs.ptr(); } + inline bool operator<=(const span_dynamic::iterator& lhr, const void* rhs) { return lhr.ptr() <= rhs; } + inline bool operator>(const span_dynamic::iterator& lhr, const void* rhs) { return lhr.ptr() > rhs; } + inline bool operator>(const void* lhr, const span_dynamic::iterator& rhs) { return lhr > rhs.ptr(); } + inline bool operator>=(const span_dynamic::iterator& lhr, const void* rhs) { return lhr.ptr() >= rhs; } + inline bool operator>=(const void* lhr, const span_dynamic::iterator& rhs) { return lhr >= rhs.ptr(); } +} + +static_assert(std::input_or_output_iterator); +static_assert(std::random_access_iterator); + +static_assert(std::ranges::random_access_range); diff --git a/projs/experiments/ecs/tests/archetype_tests.cpp b/projs/experiments/ecs/tests/archetype_tests.cpp new file mode 100644 index 00000000..d16ebd68 --- /dev/null +++ b/projs/experiments/ecs/tests/archetype_tests.cpp @@ -0,0 +1,10 @@ +#include "./ecs.exp.h" +#include "catch2/catch.hpp" + +struct A {}; +struct B {}; +struct C {}; + +TEST_CASE("Arhitype tests") { + +} diff --git a/projs/experiments/ecs/tests/id_tests.cpp b/projs/experiments/ecs/tests/id_tests.cpp new file mode 100644 index 00000000..0d4b683d --- /dev/null +++ b/projs/experiments/ecs/tests/id_tests.cpp @@ -0,0 +1,23 @@ +#include "./ecs.exp.h" +#include "catch2/catch.hpp" + +struct A {}; +struct B {}; +struct C {}; + +TEST_CASE("id TESTS") { + SECTION("Gives back ID") { + const auto res = GetTypeId(); + REQUIRE(res.id > 0); + } + SECTION("Gives back same ID") { + const auto res1 = GetTypeId(); + const auto res2 = GetTypeId(); + REQUIRE(res1.id == res2.id); + } + SECTION("Gives back different ID") { + const auto res1 = GetTypeId(); + const auto res2 = GetTypeId(); + REQUIRE(res1.id != res2.id); + } +} diff --git a/projs/experiments/ecs/tests/span_dynamic.test.cpp b/projs/experiments/ecs/tests/span_dynamic.test.cpp index e0961b56..5777cbde 100644 --- a/projs/experiments/ecs/tests/span_dynamic.test.cpp +++ b/projs/experiments/ecs/tests/span_dynamic.test.cpp @@ -1,7 +1,6 @@ +#include "span_dynamic.h" #include "catch2/catch.hpp" -#include "ecs.exp.h" - struct TestObject { int32_t a = 1; int32_t b = 2; @@ -28,60 +27,4 @@ TEST_CASE("span_dynamic tests") { it += 2; REQUIRE(it.as() == 3); } -} - -TEST_CASE("PoolAllocator tests") { - SECTION("Basic allocation") { - SH::PoolAllocator pool(sizeof(TestObject)); - auto obj = static_cast(pool.allocate()); - - REQUIRE(obj != nullptr); - } - - SECTION("Multiple allocations within a single chunk") { - SH::PoolAllocator pool(sizeof(TestObject)); - std::vector objs; - - for (int i = 0; i < 1024; ++i) { - auto obj = static_cast(pool.allocate()); - REQUIRE(obj != nullptr); - objs.push_back(obj); - } - } - - SECTION("Multiple allocations across multiple chunks") { - SH::PoolAllocator pool(sizeof(TestObject)); - std::vector objs; - - // Assuming that each chunk can hold 1024 items, - // this will require at least 2 chunks. - for (int i = 0; i < 2048; ++i) { - auto obj = static_cast(pool.allocate()); - REQUIRE(obj != nullptr); - objs.push_back(obj); - } - } - - SECTION("Allocation and deallocation") { - SH::PoolAllocator pool(sizeof(TestObject)); - auto obj1 = static_cast(pool.allocate()); - auto obj2 = static_cast(pool.allocate()); - - REQUIRE(obj1 != nullptr); - REQUIRE(obj2 != nullptr); - - pool.deallocate(obj1); - pool.deallocate(obj2); - - // After deallocation, new allocations should reuse the freed memory - auto obj3 = static_cast(pool.allocate()); - auto obj4 = static_cast(pool.allocate()); - - REQUIRE(obj3 != nullptr); - REQUIRE(obj4 != nullptr); - - // The addresses should be reused in reverse order - REQUIRE(obj3 == obj2); - REQUIRE(obj4 == obj1); - } } \ No newline at end of file diff --git a/projs/shadow/extern/catch2 b/projs/shadow/extern/catch2 index 9c541ca7..ee1450f2 160000 --- a/projs/shadow/extern/catch2 +++ b/projs/shadow/extern/catch2 @@ -1 +1 @@ -Subproject commit 9c541ca72e7857dec71d8a41b97e42c2f1c92602 +Subproject commit ee1450f268dfd5c13aa8670ba97e93cabaf2e15d From 56c6278515e9df884a88482d5f5ce4f94301eeee Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Sun, 24 Nov 2024 20:49:56 +0100 Subject: [PATCH 06/19] Work on ECS now with adding entities and components --- .clang-format | 55 ++ .idea/codeStyles/Project.xml | 52 +- .idea/vcs.xml | 1 + projs/experiments/ecs/CMakeLists.txt | 1 + projs/experiments/ecs/src/ecs.exp.cpp | 94 +++- projs/experiments/ecs/src/ecs.exp.h | 252 +++++++-- projs/experiments/ecs/src/id_system.h | 3 +- projs/experiments/ecs/src/lib/rang.hpp | 502 ++++++++++++++++++ projs/experiments/ecs/src/main.cpp | 32 +- projs/experiments/ecs/src/span_dynamic.h | 6 + .../experiments/ecs/tests/archetype_tests.cpp | 2 +- projs/experiments/ecs/tests/em_tests.cpp | 109 ++++ projs/experiments/ecs/tests/id_tests.cpp | 10 +- 13 files changed, 1006 insertions(+), 113 deletions(-) create mode 100644 .clang-format create mode 100644 projs/experiments/ecs/src/lib/rang.hpp create mode 100644 projs/experiments/ecs/tests/em_tests.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..14bdc968 --- /dev/null +++ b/.clang-format @@ -0,0 +1,55 @@ +# Generated from CLion C/C++ Code Style settings +--- +Language: Cpp +BasedOnStyle: LLVM +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignOperands: false +AlignTrailingComments: false +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: true + BeforeWhile: true + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBraces: Custom +BreakConstructorInitializers: AfterColon +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 120 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ContinuationIndentWidth: 2 +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseBlocks: true +IndentCaseLabels: true +IndentWrappedFunctionNames: true +InsertNewlineAtEOF: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +TabWidth: 2 +... diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 4fe7f294..6de452b9 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -22,48 +22,28 @@ ().id, - GetTypeId(TypeFlags::Flag).id, - GetTypeId().id, - }); + EntityManager em; + + auto a = em.AddEntity(); + + auto b = em.AddEntity(); + b.AddComponent({.x = 10, .y = 20, .z = 30}); + + + - PrintArchetype(a); + PrintEM(em); - return 0; + return 0; } diff --git a/projs/experiments/ecs/src/span_dynamic.h b/projs/experiments/ecs/src/span_dynamic.h index 7b03b577..5c394d09 100644 --- a/projs/experiments/ecs/src/span_dynamic.h +++ b/projs/experiments/ecs/src/span_dynamic.h @@ -150,6 +150,12 @@ namespace SH { return end()--; } + + iterator operator[](size_t n) const { + return iterator(p_start + n * item_size, item_size); + } + + size_t element_size() const { return item_size; } }; inline span_dynamic::iterator operator+(span_dynamic::iterator::difference_type n, span_dynamic::iterator i) diff --git a/projs/experiments/ecs/tests/archetype_tests.cpp b/projs/experiments/ecs/tests/archetype_tests.cpp index d16ebd68..b41184d5 100644 --- a/projs/experiments/ecs/tests/archetype_tests.cpp +++ b/projs/experiments/ecs/tests/archetype_tests.cpp @@ -2,7 +2,7 @@ #include "catch2/catch.hpp" struct A {}; -struct B {}; +struct Position {}; struct C {}; TEST_CASE("Arhitype tests") { diff --git a/projs/experiments/ecs/tests/em_tests.cpp b/projs/experiments/ecs/tests/em_tests.cpp new file mode 100644 index 00000000..0caa7f70 --- /dev/null +++ b/projs/experiments/ecs/tests/em_tests.cpp @@ -0,0 +1,109 @@ +#include "./ecs.exp.h" +#include "catch2/catch.hpp" + +struct A {}; +struct Position {}; +struct C {}; + +TEST_CASE("EM archetype storage") { + SECTION("Gives the correct Archetype") { + EntityManager em; + auto& a = em.GetArchetype({ + GetTypeId().id, + GetTypeId(TypeFlags::Flag).id, + GetTypeId().id, + }); + + REQUIRE(a.id > 0); + } + + SECTION("Gives the same Archetype") { + EntityManager em; + auto& a = em.GetArchetype({ + GetTypeId().id, + GetTypeId(TypeFlags::Flag).id, + GetTypeId().id, + }); + + auto& b = em.GetArchetype({ + GetTypeId().id, + GetTypeId(TypeFlags::Flag).id, + GetTypeId().id, + }); + + REQUIRE(a.id == b.id); + REQUIRE(&a == &b); + } + + SECTION("Archetype map matches internal Type list") + { + EntityManager em; + auto& a = em.GetArchetype({ + GetTypeId().id, + GetTypeId(TypeFlags::Flag).id, + GetTypeId().id, + }); + + for (auto [types, arch] : em.archetypes) + { + Types arch_types; + std::ranges::transform(arch.column_map, std::back_inserter(arch_types), [](auto i) + { + return i.first; + }); + + REQUIRE(std::ranges::equal(types, arch_types)); + } + + } +} + +TEST_CASE("Component Data") +{ + EntityManager em; + auto entity = em.AddEntity(); + + REQUIRE(entity.id != 0); + +} + + +TEST_CASE("Archetype Allocation and Deallocation") { + // Create an archetype with a specific type + Types types_list = { GetTypeId().id }; + Archetype archetype(types_list); + + SECTION("Allocation") { + size_t allocated_row = archetype.Allocate(); + REQUIRE(allocated_row != -1); + } + + SECTION("De-allocation") { + size_t allocated_row = archetype.Allocate(); + archetype.Deallocate(allocated_row); + REQUIRE(archetype.rows[allocated_row].next != -1); + } + + SECTION("Free Linked List Correctness After Multiple Allocations and Deallocations") { + std::vector allocated_rows; + + // Allocate multiple rows + for (int i = 0; i < 5; ++i) { + size_t row = archetype.Allocate(); + REQUIRE(row != -1); + allocated_rows.push_back(row); + } + + // Deallocate in reverse order + for (int i = 4; i >= 0; --i) { + archetype.Deallocate(allocated_rows[i]); + } + + // Allocate again and ensure the free list is correct + for (int i = 0; i < 5; ++i) { + size_t row = archetype.Allocate(); + REQUIRE(row == allocated_rows[i]); + } + } +} + diff --git a/projs/experiments/ecs/tests/id_tests.cpp b/projs/experiments/ecs/tests/id_tests.cpp index 0d4b683d..88271111 100644 --- a/projs/experiments/ecs/tests/id_tests.cpp +++ b/projs/experiments/ecs/tests/id_tests.cpp @@ -2,22 +2,22 @@ #include "catch2/catch.hpp" struct A {}; -struct B {}; +struct Position {}; struct C {}; TEST_CASE("id TESTS") { SECTION("Gives back ID") { const auto res = GetTypeId(); - REQUIRE(res.id > 0); + REQUIRE(res.id.id > 0); } SECTION("Gives back same ID") { const auto res1 = GetTypeId(); const auto res2 = GetTypeId(); - REQUIRE(res1.id == res2.id); + REQUIRE(res1.id.id == res2.id.id); } SECTION("Gives back different ID") { const auto res1 = GetTypeId(); - const auto res2 = GetTypeId(); - REQUIRE(res1.id != res2.id); + const auto res2 = GetTypeId(); + REQUIRE(res1.id.id != res2.id.id); } } From 7de129a49a817bcfae16e859d5efaccc145da6bd Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Sun, 24 Nov 2024 22:51:52 +0100 Subject: [PATCH 07/19] Unit tests and small fix Add more unit tests for the Archetypes and fix last item in free jump list --- projs/experiments/ecs/src/ecs.exp.cpp | 3 +- projs/experiments/ecs/src/ecs.exp.h | 2 +- .../experiments/ecs/tests/archetype.tests.cpp | 169 ++++++++++++++++++ .../experiments/ecs/tests/archetype_tests.cpp | 10 -- projs/experiments/ecs/tests/em_tests.cpp | 37 ---- 5 files changed, 172 insertions(+), 49 deletions(-) create mode 100644 projs/experiments/ecs/tests/archetype.tests.cpp delete mode 100644 projs/experiments/ecs/tests/archetype_tests.cpp diff --git a/projs/experiments/ecs/src/ecs.exp.cpp b/projs/experiments/ecs/src/ecs.exp.cpp index e5fa032c..ad63d320 100644 --- a/projs/experiments/ecs/src/ecs.exp.cpp +++ b/projs/experiments/ecs/src/ecs.exp.cpp @@ -32,10 +32,11 @@ Archetype::Archetype(const Types& types_list): id(next_id++) empty = 0; size_t next = 1; - for (int i = 0; i < PAGE_SIZE; ++i) + for (int i = 0; i < PAGE_SIZE-1; ++i) { rows.push_back({.next = next++}); } + rows.push_back({.next = (size_t)-1}); } diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 691b32a8..0a99ef66 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -153,7 +153,7 @@ class Archetype assert(dest_column.element_size() == source_column.element_size() && "Element sizes don't match"); - memcpy(dest, src, source_column.element_size()); + std::memcpy(dest, src, source_column.element_size()); ++source_index; ++dest_index; diff --git a/projs/experiments/ecs/tests/archetype.tests.cpp b/projs/experiments/ecs/tests/archetype.tests.cpp new file mode 100644 index 00000000..34071f3c --- /dev/null +++ b/projs/experiments/ecs/tests/archetype.tests.cpp @@ -0,0 +1,169 @@ +#include "./ecs.exp.h" +#include "catch2/catch.hpp" + +struct A {}; +struct Position {}; +struct C {}; + +TEST_CASE("Archetype Allocation and Deallocation", "[Archetype]") { + // Create an archetype with a specific type + Types types_list = { GetTypeId(TypeFlags::None).id }; + Archetype archetype(types_list); + + SECTION("Allocation") { + size_t allocated_row = archetype.Allocate(); + REQUIRE(allocated_row != -1); + } + + SECTION("Deallocation") { + size_t allocated_row = archetype.Allocate(); + archetype.Deallocate(allocated_row); + REQUIRE(archetype.rows[allocated_row].next != -1); + } + + SECTION("Free Linked List Correctness After Multiple Allocations and Deallocations") { + std::vector allocated_rows; + + // Allocate multiple rows + for (int i = 0; i < 5; ++i) { + size_t row = archetype.Allocate(); + REQUIRE(row != -1); + allocated_rows.push_back(row); + } + + // Deallocate in reverse order + for (int i = 4; i >= 0; --i) { + archetype.Deallocate(allocated_rows[i]); + } + + // Allocate again and ensure the free list is correct + for (int i = 0; i < 5; ++i) { + size_t row = archetype.Allocate(); + REQUIRE(row == allocated_rows[i]); + } + } + + SECTION("Allocation Beyond Capacity") { + for (size_t i = 0; i < PAGE_SIZE; ++i) { + size_t row = archetype.Allocate(); + REQUIRE(row != -1); + } + // Attempt to allocate beyond capacity + size_t row = archetype.Allocate(); + REQUIRE(row == -1); + } + + // SECTION("Deallocation of Invalid Row") { + // // Deallocate an invalid row index + // REQUIRE_THROWS_AS(archetype.Deallocate(PAGE_SIZE + 1), std::exception); + // } +} +struct TestComponent { + int value; +}; + +TEST_CASE("Archetype Column Manipulation") { + Types types_list = { GetTypeId(TypeFlags::None).id, GetTypeId(TypeFlags::None).id }; + Archetype archetype(types_list); + + struct TestComponent1 { + int value; + }; + + struct TestComponent2 { + float value; + }; + + SECTION("Add and Retrieve Components") { + size_t row = archetype.Allocate(); + SH::span_dynamic& column1 = archetype.GetColumn(types_list[0]); + new(column1[row].as_ptr()) TestComponent1{42}; + + SH::span_dynamic& column2 = archetype.GetColumn(types_list[1]); + new(column2[row].as_ptr()) TestComponent2{3.14f}; + + REQUIRE(column1[row].as().value == 42); + REQUIRE(column2[row].as().value == 3.14f); + } + + SECTION("GetColumn with Invalid TypeId") { + TypeId invalid_type{999, TypeFlags::None, 0}; + REQUIRE_THROWS_AS(archetype.GetColumn(invalid_type), std::out_of_range); + } +} + +TEST_CASE("Archetype State Validation") { + Types types_list = { GetTypeId().id }; + Archetype archetype(types_list); + + SECTION("State After Allocation and Deallocation") { + size_t row1 = archetype.Allocate(); + size_t row2 = archetype.Allocate(); + REQUIRE(row1 != row2); + + archetype.Deallocate(row1); + REQUIRE(archetype.rows[row1].next == 2); + + size_t row3 = archetype.Allocate(); + REQUIRE(row3 == row1); + } +} + +TEST_CASE("Archetype Memory Consistency") { + Types types_list = { GetTypeId().id }; + Archetype archetype(types_list); + + SECTION("Memory Allocation and Deallocation Consistency") { + size_t row1 = archetype.Allocate(); + SH::span_dynamic& column = archetype.GetColumn(types_list[0]); + new(column[row1].as_ptr()) TestComponent{42}; + + archetype.Deallocate(row1); + size_t row2 = archetype.Allocate(); + REQUIRE(row1 == row2); + + new(column[row2].as_ptr()) TestComponent{84}; + REQUIRE(column[row2].as().value == 84); + } +} + +struct ComplexComponent { + std::string value; + ComplexComponent(const std::string& val) : value(val) {} +}; + +TEST_CASE("Archetype Copy From for Complex Types") { + auto type_id = GetTypeId().id; + Types types_list_1 = { type_id }; + Archetype archetype_1(types_list_1); + + Types types_list_2 = { type_id }; + Archetype archetype_2(types_list_2); + + archetype_1.Allocate(); + SH::span_dynamic& column = archetype_1.GetColumn(types_list_1[0]); + new(column[0].as_ptr()) ComplexComponent{"Hello, World!"}; + + SECTION("CopyFrom another archetype with complex type") { + size_t target_row = archetype_2.Allocate(); + archetype_2.CopyFrom(archetype_1, 0, target_row); + + SH::span_dynamic& target_column = archetype_2.GetColumn(types_list_2[0]); + REQUIRE(target_column[target_row].as().value == "Hello, World!"); + } +} + +TEST_CASE("Archetype Destructor and Cleanup") { + Types types_list = { GetTypeId(TypeFlags::None).id }; + + SECTION("Cleanup on Destruction") { + Archetype* archetype = new Archetype(types_list); + size_t row = archetype->Allocate(); + SH::span_dynamic& column = archetype->GetColumn(types_list[0]); + new(column[row].as_ptr()) int{42}; + + delete archetype; + // If there are memory leaks, the test runner will report them. + } +} + diff --git a/projs/experiments/ecs/tests/archetype_tests.cpp b/projs/experiments/ecs/tests/archetype_tests.cpp deleted file mode 100644 index b41184d5..00000000 --- a/projs/experiments/ecs/tests/archetype_tests.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "./ecs.exp.h" -#include "catch2/catch.hpp" - -struct A {}; -struct Position {}; -struct C {}; - -TEST_CASE("Arhitype tests") { - -} diff --git a/projs/experiments/ecs/tests/em_tests.cpp b/projs/experiments/ecs/tests/em_tests.cpp index 0caa7f70..c296c290 100644 --- a/projs/experiments/ecs/tests/em_tests.cpp +++ b/projs/experiments/ecs/tests/em_tests.cpp @@ -68,42 +68,5 @@ TEST_CASE("Component Data") } -TEST_CASE("Archetype Allocation and Deallocation") { - // Create an archetype with a specific type - Types types_list = { GetTypeId().id }; - Archetype archetype(types_list); - - SECTION("Allocation") { - size_t allocated_row = archetype.Allocate(); - REQUIRE(allocated_row != -1); - } - - SECTION("De-allocation") { - size_t allocated_row = archetype.Allocate(); - archetype.Deallocate(allocated_row); - REQUIRE(archetype.rows[allocated_row].next != -1); - } - - SECTION("Free Linked List Correctness After Multiple Allocations and Deallocations") { - std::vector allocated_rows; - - // Allocate multiple rows - for (int i = 0; i < 5; ++i) { - size_t row = archetype.Allocate(); - REQUIRE(row != -1); - allocated_rows.push_back(row); - } - // Deallocate in reverse order - for (int i = 4; i >= 0; --i) { - archetype.Deallocate(allocated_rows[i]); - } - - // Allocate again and ensure the free list is correct - for (int i = 0; i < 5; ++i) { - size_t row = archetype.Allocate(); - REQUIRE(row == allocated_rows[i]); - } - } -} From 585551b6b959664a1b94a6d8e10d8ab43e712275 Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Sun, 24 Nov 2024 23:28:16 +0100 Subject: [PATCH 08/19] Add Component removal --- projs/experiments/ecs/src/ecs.exp.cpp | 20 +++++-- projs/experiments/ecs/src/ecs.exp.h | 77 +++++++++++++++++++++------ projs/experiments/ecs/src/main.cpp | 25 ++++++--- 3 files changed, 97 insertions(+), 25 deletions(-) diff --git a/projs/experiments/ecs/src/ecs.exp.cpp b/projs/experiments/ecs/src/ecs.exp.cpp index ad63d320..9083ef79 100644 --- a/projs/experiments/ecs/src/ecs.exp.cpp +++ b/projs/experiments/ecs/src/ecs.exp.cpp @@ -30,13 +30,13 @@ Archetype::Archetype(const Types& types_list): id(next_id++) } }); - empty = 0; + next_free = 0; size_t next = 1; for (int i = 0; i < PAGE_SIZE-1; ++i) { rows.push_back({.next = next++}); } - rows.push_back({.next = (size_t)-1}); + rows.push_back({.next = (size_t)-1, .in_use = false}); } @@ -76,7 +76,7 @@ void PrintArchetype(const Archetype& a) for (int i = 0; i < a.rows.size(); ++i) { - if(a.rows[i].next == -1) + if(a.rows[i].in_use) { std::cout << rang::fg::green << "â–ˆ" << rang::style::reset; } @@ -110,5 +110,19 @@ void PrintEM(EntityManager& em) std::cout << "Entity: " << id << " Arch: " << record.archetype->id << " Row: " << record.row << std::endl; + + + for (auto map : record.archetype->column_map) + { + if(map.second >= 0) + { + std::cout << "\t"; + const auto& comp = record.archetype->columns[map.second][record.row].as(); + comp.Print(); + } + } + + + std::cout << std::endl; } } diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 0a99ef66..32db39ea 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -44,7 +44,9 @@ class Entity class Component { - +public: + virtual ~Component() = default; + virtual void Print() const = 0; }; // #################################################### @@ -74,6 +76,7 @@ struct RowMeta { // EntityId id; size_t next; + bool in_use; }; constexpr size_t PAGE_SIZE = 2*2*2*2*2*2; @@ -92,7 +95,7 @@ class Archetype ColumnMap column_map; - int empty; + int next_free; std::vector rows; std::vector columns; @@ -113,13 +116,14 @@ class Archetype */ size_t Allocate() { - if(empty == -1) + if(next_free == -1) { return -1; } - auto row = empty; - empty = rows[row].next; + auto row = next_free; + next_free = rows[row].next; rows[row].next = -1; + rows[row].in_use = true; return row; } @@ -127,8 +131,9 @@ class Archetype void Deallocate(size_t row) { assert(row < rows.size() && "Invalid row index"); - rows[row].next = empty; - empty = row; + rows[row].next = next_free; + rows[row].in_use = false; + next_free = row; } void CopyFrom(const Archetype &source, const size_t src_row, const size_t target_row) const @@ -178,16 +183,16 @@ struct EntityRef EntityManager *em; EntityId id; - EntityRef(EntityManager *em, const EntityId& id) : em(em), id(id) {} + EntityRef(EntityManager *em, const EntityId &id) : em(em), id(id) {} template - EntityRef& AddComponent(T&& val) - { - em->AddComponent(id, std::forward(val)); - return *this; - } + EntityRef &AddComponent(T &&val); + + template + EntityRef &RemoveComponent(); }; + template struct EntityRefTyped : EntityRef { @@ -227,10 +232,10 @@ class EntityManager size_t row; }; - EntityCreationResult CreateEntity(Types type_id) + EntityCreationResult CreateEntity(const Types &type_id) { - auto &a = GetArchetype(type_id); - auto row = a.Allocate(); + auto &a = GetArchetype(type_id); + const auto row = a.Allocate(); EntityId id = GetEntityId(); @@ -276,12 +281,52 @@ class EntityManager archetype.CopyFrom(*record.archetype, record.row, target_row); record.archetype->Deallocate(record.row); + T* ptr = archetype.GetColumn(type_id)[target_row].as_ptr(); + new(ptr)T(value); + + record.row = target_row; + record.archetype = &archetype; + } + + template + void RemoveComponent(EntityId entity_id) + { + const TypeId type_id = GetTypeId().id; + auto& record = entities[entity_id]; + + Types new_types = record.archetype->types; + std::erase(new_types, type_id); + new_types = sortTypes(new_types); + + auto &archetype = GetArchetype(new_types); + const auto target_row = archetype.Allocate(); + + archetype.CopyFrom(*record.archetype, record.row, target_row); + + record.archetype->Deallocate(record.row); + record.row = target_row; record.archetype = &archetype; } }; + +template +EntityRef &EntityRef::AddComponent(T &&val) +{ + em->AddComponent(id, std::forward(val)); + return *this; +} +template +EntityRef &EntityRef::RemoveComponent() +{ + em->RemoveComponent(id); + return *this; +} + + + void PrintArchetype(const Archetype &a); void PrintEM(EntityManager &em); diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp index 998535b0..eca56100 100644 --- a/projs/experiments/ecs/src/main.cpp +++ b/projs/experiments/ecs/src/main.cpp @@ -8,11 +8,25 @@ struct A : Entity char sentinel[9] = "Entity A"; }; -struct Position : Component +struct Position final : Component { int x, y, z = 0; + Position(const int& x, const int& y, const int& z) : x(x), y(y), z(z) {} + + void Print() const override + { + printf("Position (%d, %d, %d)\n", x, y, z); + } +}; +struct Name final : Component +{ + char sentinel[5] = "Name"; + + void Print() const override + { + printf("Name: %s\n", sentinel); + } }; -struct C : Component {}; int main() { @@ -22,10 +36,9 @@ int main() { auto a = em.AddEntity(); auto b = em.AddEntity(); - b.AddComponent({.x = 10, .y = 20, .z = 30}); - - - + b.AddComponent({ 10, 20, 30}) + .AddComponent({}) + .RemoveComponent(); PrintEM(em); From c7c5e3df39db3269e8ad6483e608fa9e9daadd5c Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Thu, 5 Dec 2024 17:51:27 +0100 Subject: [PATCH 09/19] Reworked type Id and added basic relation --- projs/experiments/ecs/CMakeLists.txt | 3 + projs/experiments/ecs/src/ecs.exp.cpp | 82 +++++- projs/experiments/ecs/src/ecs.exp.h | 247 +++++++++++++----- projs/experiments/ecs/src/id_system.cpp | 12 +- projs/experiments/ecs/src/id_system.h | 68 ++--- projs/experiments/ecs/src/main.cpp | 14 +- .../experiments/ecs/tests/archetype.tests.cpp | 201 +++++++------- projs/experiments/ecs/tests/em_tests.cpp | 46 ++-- projs/experiments/ecs/tests/id_tests.cpp | 6 +- 9 files changed, 429 insertions(+), 250 deletions(-) diff --git a/projs/experiments/ecs/CMakeLists.txt b/projs/experiments/ecs/CMakeLists.txt index 7e703ad7..29784173 100644 --- a/projs/experiments/ecs/CMakeLists.txt +++ b/projs/experiments/ecs/CMakeLists.txt @@ -26,5 +26,8 @@ add_executable(experiment-ecs-tests) target_link_libraries(experiment-ecs-tests Catch2::Catch2 ) +target_compile_options(experiment-ecs-tests + PUBLIC -fconcepts-diagnostics-depth=4 +) target_sources(experiment-ecs-tests PUBLIC ${SOURCES_TESTS}) target_include_directories(experiment-ecs-tests PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src) \ No newline at end of file diff --git a/projs/experiments/ecs/src/ecs.exp.cpp b/projs/experiments/ecs/src/ecs.exp.cpp index 9083ef79..3b8604b7 100644 --- a/projs/experiments/ecs/src/ecs.exp.cpp +++ b/projs/experiments/ecs/src/ecs.exp.cpp @@ -3,19 +3,29 @@ #include #include "lib/rang.hpp" -Archetype::Id Archetype::next_id = 0; +std::size_t std::hash::operator()(const NodeType& s) const noexcept +{ + return std::hash{}(s.typeId); +} Types sortTypes(Types t) { std::ranges::sort(t, [](auto a, auto b) { return a < b; }); return t; } -Archetype::Archetype(const Types& types_list): id(next_id++) +Archetype::Id Archetype::next_id = 0; + +Archetype::Id GetNextId() +{ + return Archetype::next_id++; +} + +Archetype::Archetype(const Types& types_list): id(GetNextId()) { - size_t next_c = 0; + size_t next_c = 0; types = sortTypes(types_list); - std::ranges::for_each(types, [&](const TypeId& type) + std::ranges::for_each(types, [&](const NodeType& type) { if(test(type.flags, TypeFlags::Flag)) { @@ -24,7 +34,7 @@ Archetype::Archetype(const Types& types_list): id(next_id++) else { this->column_map.insert({type, next_c++}); - const TypeInfo& info = GetTypeInfoById(type); + const TypeInfo& info = GetTypeInfoById(type.typeId); void* page = malloc(info.size * PAGE_SIZE); this->columns.push_back({page, info.size, PAGE_SIZE}); } @@ -42,20 +52,62 @@ Archetype::Archetype(const Types& types_list): id(next_id++) -Archetype& EntityManager::GetArchetype(const Types& types) +Archetype &EntityManager::GetArchetype(const Types &types) { - if(archetypes.contains(types)) + if (archetypes.contains(types)) { return archetypes.at(types); } Archetype a(types); - const auto res = archetypes.emplace(types, Archetype{types}); + const auto res = archetypes.try_emplace(types, types); return res.first->second; } +EntityManager::EntityCreationResult EntityManager::CreateEntity(const Types &type_id) +{ + auto &a = GetArchetype(type_id); + const auto row = a.Allocate(); + + EntityId id = GetEntityId(); + + auto [fst, snd] = entities.emplace(id, EntityRecord{&a, row}); + return { + .id = id, + .archetype = &a, + .row = row, + }; +} + +void EntityManager::MoveEntity(const EntityId &id, const Types &types, EntityCreationResult &result) +{ + auto &record = entities[id]; + auto &archetype = GetArchetype(types); + const auto target_row = archetype.Allocate(); + archetype.CopyFrom(*record.archetype, record.row, target_row); + + record.archetype->Deallocate(record.row); + + record.row = target_row; + record.archetype = &archetype; + + result.id = id; + result.archetype = &archetype; + result.row = record.row; +} + +EntityRef EntityManager::AddEntity() +{ + auto [id, a, row] = CreateEntity({}); + return {this, id}; +} + + +// ################################### +// Print Helpers +//################################### void PrintArchetype(const Archetype& a) { @@ -64,7 +116,7 @@ void PrintArchetype(const Archetype& a) for (const auto& [type, column] : a.column_map) { std::cout << " \t" - << "Type: " << GetTypeNameByID(type) << " (" << type.id << ")" + << "Type: " << GetTypeNameByID(type.typeId) << " (" << type.typeId << ")" <<" Column: " << column; if(column >= 0) { @@ -117,12 +169,20 @@ void PrintEM(EntityManager& em) if(map.second >= 0) { std::cout << "\t"; - const auto& comp = record.archetype->columns[map.second][record.row].as(); + const auto& comp = record.archetype->columns[map.second][record.row].as>(); comp.Print(); } + else + { + std::cout << "\t"; + std::cout << GetTypeNameByID(map.first.typeId); + if (test(map.first.flags, TypeFlags::Relation)) + { + std::cout << " -> " << map.first.entity; + } + } } - std::cout << std::endl; } } diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 32db39ea..732fb624 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -1,29 +1,140 @@ +#pragma once + #include #include #include +#include #include #include +#include #include #include #include #include #include -#include #include "span_dynamic.h" #include "id_system.h" -#include +#define __OUT__ + +/** +* This file contains the Entity System +* The game world is built up from nodes +* Each Node has type +* There are N types of nodes +* - Entites +* - Components +* - Flags +* - Connections +*/ + + + +using EntityId = uint32_t; + + +/** +* Flag enum used to specify ECS node properties +* it is a 4 bit field the est of the uint8 should not be used as it will be cut of +*/ +enum class TypeFlags : std::uint8_t { + None = 0, + Flag = 1 << 1, + Relation = 1 << 2, + Unused = 1 << 3, + Unused2 = 1 << 4, + + SimpleRelation = Flag | Relation, +}; + +inline TypeFlags operator|(TypeFlags lhs, TypeFlags rhs) { + return static_cast( + static_cast>(lhs) | + static_cast>(rhs) + ); +} +inline TypeFlags operator&(const TypeFlags& lhs, const TypeFlags& rhs) +{ + return static_cast( + static_cast>(lhs) & + static_cast>(rhs) + ); +} + +inline bool test(const TypeFlags& lhs, const TypeFlags& rhs) +{ + return static_cast>(lhs & rhs); +} + +template +concept HasTypeFlags = requires +{ + T::Flags; + std::is_same_v; +}; + +/** +* Type id used by the Entity system to identify the type of the node +* A node can be either : +* - Simple data +* - A connection type +*/ +struct __attribute__((packed)) NodeType +{ + TypeId typeId : 28; + TypeFlags flags : 4; + uint32_t entity; +}; +static_assert(sizeof(NodeType) == sizeof(uint64_t)); + +inline int operator<(const NodeType& lhs, const NodeType& rhs){ return rhs.typeId > lhs.typeId; } +inline bool operator==(const NodeType& lhs, const NodeType& rhs) { return lhs.typeId == rhs.typeId; } + +template <> +struct std::hash +{ + std::size_t operator()(const NodeType& s) const noexcept; +}; + +template +NodeType GetNodeType() +{ + const auto id = GetTypeId().id; + const TypeFlags flags = T::Flags; + const NodeType node = { + .typeId = id, + .flags = flags, + .entity = 0, + }; + return node; +} + +template +NodeType GetRelationType(const EntityId& other) +{ + const auto id = GetTypeId().id; + const TypeFlags flags = T::Flags; + const NodeType node = { + .typeId = id, + .flags = flags, + .entity = other, + }; + return node; +} + + + /* - * Archetype : T1 (T2, T3, T4) + * Archetype : T1, (T2, T3, T4) * | self (T1) | comp 1 (T2) | comp 2 (T3) | comp 3 (T4) | * | T1: 1 | T2: 1 | T3: 1 | T4: 1 | * | T1: 2 | T2: 2 | T3: 2 | T4: 2 | * | T1: 3 | T2: 3 | T3: 3 | T4: 3 | * - * Archetype : (T1, T2, T3) + * Archetype : T1, (T2, T3) * | self (T1) | comp 1 (T2) | comp 2 (T3) | * | T1: 4 | T2: 4 | T3: 4 | * | T1: 5 | T2: 5 | T3: 5 | @@ -35,28 +146,55 @@ // ################## Entity base classes ############# // #################################################### -using EntityId = uint32_t; +template +class Component +{ +public: + static constexpr TypeFlags Flags = F; -class Entity + virtual ~Component() = default; + virtual void Print() const = 0; +}; + +class Entity : Component<> { }; -class Component + +struct ChildOf final : Component { -public: - virtual ~Component() = default; - virtual void Print() const = 0; + void Print() const override + { + printf("ChildOf"); + } }; // #################################################### // ################### Archetype ###################### // #################################################### -using Types = std::vector; +// TODO: Find a better sorted storage for types +using Types = std::vector; Types sortTypes(Types t); +inline Types addType(const Types &t, const NodeType id) +{ + Types new_types = t; + new_types.push_back(id); + new_types = sortTypes(new_types); + return new_types; +} + +inline Types removeType(const Types &t, const NodeType id) +{ + Types new_types = t; + std::erase(new_types, id); + new_types = sortTypes(new_types); + return new_types; +} + template <> struct std::hash { @@ -65,16 +203,16 @@ struct std::hash std::size_t seed = vec.size(); for (auto &i : vec) { - seed ^= i.id + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= i.typeId + 0x9e3779b9 + (seed << 6) + (seed >> 2); } return seed; } }; + struct RowMeta { - // EntityId id; size_t next; bool in_use; }; @@ -87,7 +225,7 @@ class Archetype using Id = uint32_t; static Id next_id; - using ColumnMap = std::map; + using ColumnMap = std::map; Id id; @@ -102,11 +240,11 @@ class Archetype Archetype() = default; - Archetype(const std::initializer_list types_list) : Archetype(std::vector(types_list)) {} + Archetype(const std::initializer_list types_list) : Archetype(std::vector(types_list)) {} explicit Archetype(const Types &types_list); - SH::span_dynamic& GetColumn(TypeId id) + SH::span_dynamic& GetColumn(NodeType id) { return columns[column_map.at(id)]; } @@ -190,6 +328,9 @@ struct EntityRef template EntityRef &RemoveComponent(); + + template + T& GetComponent(); }; @@ -232,31 +373,16 @@ class EntityManager size_t row; }; - EntityCreationResult CreateEntity(const Types &type_id) - { - auto &a = GetArchetype(type_id); - const auto row = a.Allocate(); + EntityCreationResult CreateEntity(const Types &type_id); - EntityId id = GetEntityId(); + void MoveEntity(const EntityId &id, const Types &types, __OUT__ EntityCreationResult &result); - auto [fst, snd] = entities.emplace(id, EntityRecord{&a, row}); - return { - .id = id, - .archetype = &a, - .row = row, - }; - } - - EntityRef AddEntity() - { - auto [id, a, row] = CreateEntity({}); - return {this, id}; - } + EntityRef AddEntity(); template EntityRefTyped AddEntity() { - TypeId type_id = GetTypeId().id; + NodeType type_id = GetTypeId().id; auto [id, a, row] = CreateEntity({type_id}); T* ptr = a->GetColumn(type_id)[row].as_ptr(); @@ -268,46 +394,41 @@ class EntityManager template void AddComponent(EntityId entity_id, T&& value) { - const TypeId type_id = GetTypeId().id; - + const NodeType type_id = GetNodeType(); auto& record = entities[entity_id]; - Types new_types = record.archetype->types; - new_types.push_back(type_id); - new_types = sortTypes(new_types); - auto &archetype = GetArchetype(new_types); - const auto target_row = archetype.Allocate(); + Types new_types = addType(record.archetype->types, type_id); - archetype.CopyFrom(*record.archetype, record.row, target_row); - record.archetype->Deallocate(record.row); + EntityCreationResult res; + MoveEntity(entity_id, new_types, res); - T* ptr = archetype.GetColumn(type_id)[target_row].as_ptr(); + T* ptr = res.archetype->GetColumn(type_id)[res.row].as_ptr(); new(ptr)T(value); - - record.row = target_row; - record.archetype = &archetype; } template void RemoveComponent(EntityId entity_id) { - const TypeId type_id = GetTypeId().id; + const NodeType type_id = GetTypeId().id; auto& record = entities[entity_id]; - Types new_types = record.archetype->types; - std::erase(new_types, type_id); - new_types = sortTypes(new_types); + Types new_types = removeType(record.archetype->types, type_id); - auto &archetype = GetArchetype(new_types); - const auto target_row = archetype.Allocate(); - - archetype.CopyFrom(*record.archetype, record.row, target_row); + EntityCreationResult res; + MoveEntity(entity_id, new_types, res); + } - record.archetype->Deallocate(record.row); + template + void AddRelation(EntityId entity_id, EntityId other) + { + const NodeType type_id = GetRelationType(other); + auto& record = entities[entity_id]; + Types new_types = addType(record.archetype->types, type_id); - record.row = target_row; - record.archetype = &archetype; + EntityCreationResult res; + MoveEntity(entity_id, new_types, res); } + }; @@ -324,7 +445,13 @@ EntityRef &EntityRef::RemoveComponent() em->RemoveComponent(id); return *this; } - +template +T& EntityRef::GetComponent() +{ + const auto& record = em->entities.at(id); + const auto& column = record.archetype->GetColumn(GetNodeType()); + return column[record.row].template as(); +} void PrintArchetype(const Archetype &a); diff --git a/projs/experiments/ecs/src/id_system.cpp b/projs/experiments/ecs/src/id_system.cpp index 2691218b..77845f01 100644 --- a/projs/experiments/ecs/src/id_system.cpp +++ b/projs/experiments/ecs/src/id_system.cpp @@ -2,12 +2,7 @@ std::unordered_map typeMap; -TypeId next_id({.id = 1}); - -std::size_t std::hash::operator()(const TypeId& s) const noexcept -{ - return std::hash{}(s.id); -} +uint32_t next_id = 1; TypeInfo GetTypeInfoById(const TypeId& id) { @@ -24,8 +19,3 @@ std::string GetTypeNameByID(TypeId id) return i.second.id == id; })->first; } - -void __jumpTypeID(uint32_t n) -{ - next_id.id += n; -} diff --git a/projs/experiments/ecs/src/id_system.h b/projs/experiments/ecs/src/id_system.h index a87bae34..14f79edb 100644 --- a/projs/experiments/ecs/src/id_system.h +++ b/projs/experiments/ecs/src/id_system.h @@ -1,62 +1,24 @@ #pragma once -#include -#include -#include -#include +#include #include #include -#include -#include #include +#include #include - -enum class TypeFlags : std::uint8_t { - None = 0, - Flag = 1 << 1, - Relation = 1 << 2, - Unused = 1 << 3, - Unused2 = 1 << 4, -}; - -inline TypeFlags operator|(TypeFlags lhs, TypeFlags rhs) { - return static_cast( - static_cast>(lhs) | - static_cast>(rhs) - ); -} -inline TypeFlags operator&(const TypeFlags& lhs, const TypeFlags& rhs) -{ - return static_cast( - static_cast>(lhs) & - static_cast>(rhs) - ); -} -inline bool test(const TypeFlags& lhs, const TypeFlags& rhs) -{ - return static_cast>(lhs & rhs); -} +#include +#include +#include +#include -struct __attribute__((packed)) TypeId -{ - uint32_t id : 28; - TypeFlags flags : 4; - uint32_t entity; -}; -static_assert(sizeof(TypeId) == sizeof(uint64_t)); -inline int operator<(const TypeId& lhs, const TypeId& rhs){ return rhs.id > lhs.id; } -inline bool operator==(const TypeId& lhs, const TypeId& rhs) { return lhs.id == rhs.id; } - -template <> -struct std::hash -{ - std::size_t operator()(const TypeId& s) const noexcept;; -}; +using TypeId = uint32_t; +//inline int operator<(const TypeId& lhs, const TypeId& rhs){ return rhs > lhs; } +//inline bool operator==(const TypeId& lhs, const TypeId& rhs) { return lhs == rhs; } struct TypeInfo @@ -65,21 +27,20 @@ struct TypeInfo std::string name; size_t size; }; +extern std::unordered_map typeMap; +extern uint32_t next_id; -extern std::unordered_map typeMap; - -extern TypeId next_id; template -TypeInfo GetTypeId(const TypeFlags& flags = TypeFlags::None) +TypeInfo GetTypeId() { const char* name = typeid(T).name(); if(!typeMap.contains(name)) { TypeInfo newId = { - .id = {.id = next_id.id++, .flags = flags}, + .id = next_id++, .name = name, .size = sizeof(T), }; @@ -93,4 +54,5 @@ TypeInfo GetTypeInfoById(const TypeId& id); std::string GetTypeNameByID(TypeId id); -void __jumpTypeID(uint32_t n); + + diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp index eca56100..49cfcf81 100644 --- a/projs/experiments/ecs/src/main.cpp +++ b/projs/experiments/ecs/src/main.cpp @@ -8,7 +8,7 @@ struct A : Entity char sentinel[9] = "Entity A"; }; -struct Position final : Component +struct Position final : Component<> { int x, y, z = 0; Position(const int& x, const int& y, const int& z) : x(x), y(y), z(z) {} @@ -18,7 +18,7 @@ struct Position final : Component printf("Position (%d, %d, %d)\n", x, y, z); } }; -struct Name final : Component +struct Name final : Component<> { char sentinel[5] = "Name"; @@ -28,7 +28,6 @@ struct Name final : Component } }; - int main() { EntityManager em; @@ -36,9 +35,12 @@ int main() { auto a = em.AddEntity(); auto b = em.AddEntity(); - b.AddComponent({ 10, 20, 30}) - .AddComponent({}) - .RemoveComponent(); + b.AddComponent({ 10, 22, 32}) + .AddComponent({}); + + b.GetComponent().x = 100; + + em.AddRelation(b.id,a.id); PrintEM(em); diff --git a/projs/experiments/ecs/tests/archetype.tests.cpp b/projs/experiments/ecs/tests/archetype.tests.cpp index 34071f3c..2b1394f6 100644 --- a/projs/experiments/ecs/tests/archetype.tests.cpp +++ b/projs/experiments/ecs/tests/archetype.tests.cpp @@ -1,50 +1,73 @@ #include "./ecs.exp.h" #include "catch2/catch.hpp" -struct A {}; -struct Position {}; -struct C {}; +struct A : Component<> +{ + int a; + float b; + A(int a, float b) : a(a), b(b) {} + void Print() const override {}; +}; +struct B : Component<> +{ + std::string value; + explicit B(const char * str): value(str) {}; + void Print() const override {} +}; +struct C : Component<> +{ + void Print() const override {} +}; -TEST_CASE("Archetype Allocation and Deallocation", "[Archetype]") { +TEST_CASE("Archetype Allocation and De-allocation", "[Archetype]") +{ // Create an archetype with a specific type - Types types_list = { GetTypeId(TypeFlags::None).id }; + Types types_list = {GetNodeType()}; Archetype archetype(types_list); - SECTION("Allocation") { + SECTION("Allocation") + { size_t allocated_row = archetype.Allocate(); REQUIRE(allocated_row != -1); } - SECTION("Deallocation") { + SECTION("Deallocation") + { size_t allocated_row = archetype.Allocate(); archetype.Deallocate(allocated_row); REQUIRE(archetype.rows[allocated_row].next != -1); } - SECTION("Free Linked List Correctness After Multiple Allocations and Deallocations") { + SECTION("Free Linked List Correctness After Multiple Allocations and De-allocations") + { std::vector allocated_rows; // Allocate multiple rows - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < 5; ++i) + { size_t row = archetype.Allocate(); REQUIRE(row != -1); allocated_rows.push_back(row); } // Deallocate in reverse order - for (int i = 4; i >= 0; --i) { + for (int i = 4; i >= 0; --i) + { archetype.Deallocate(allocated_rows[i]); } // Allocate again and ensure the free list is correct - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < 5; ++i) + { size_t row = archetype.Allocate(); REQUIRE(row == allocated_rows[i]); } } - SECTION("Allocation Beyond Capacity") { - for (size_t i = 0; i < PAGE_SIZE; ++i) { + SECTION("Allocation Beyond Capacity") + { + for (size_t i = 0; i < PAGE_SIZE; ++i) + { size_t row = archetype.Allocate(); REQUIRE(row != -1); } @@ -58,112 +81,108 @@ TEST_CASE("Archetype Allocation and Deallocation", "[Archetype]") { // REQUIRE_THROWS_AS(archetype.Deallocate(PAGE_SIZE + 1), std::exception); // } } -struct TestComponent { - int value; -}; - -TEST_CASE("Archetype Column Manipulation") { - Types types_list = { GetTypeId(TypeFlags::None).id, GetTypeId(TypeFlags::None).id }; - Archetype archetype(types_list); - struct TestComponent1 { - int value; - }; - struct TestComponent2 { - float value; - }; +TEST_CASE("Archetype Column Manipulation", "[Archetype]") +{ + const Types types_list = {GetNodeType(), GetNodeType()}; + Archetype archetype(types_list); - SECTION("Add and Retrieve Components") { - size_t row = archetype.Allocate(); - SH::span_dynamic& column1 = archetype.GetColumn(types_list[0]); - new(column1[row].as_ptr()) TestComponent1{42}; + SECTION("Components Constructable in column") + { + const size_t row = archetype.Allocate(); + const SH::span_dynamic &column1 = archetype.GetColumn(types_list[0]); + new (column1[row].as_ptr()) A{42, 10.3}; - SH::span_dynamic& column2 = archetype.GetColumn(types_list[1]); - new(column2[row].as_ptr()) TestComponent2{3.14f}; + SH::span_dynamic &column2 = archetype.GetColumn(types_list[1]); + new (column2[row].as_ptr()) B{"asd"}; - REQUIRE(column1[row].as().value == 42); - REQUIRE(column2[row].as().value == 3.14f); - } + REQUIRE(column1[row].as().a == 42); + REQUIRE(column2[row].as().value == "asd"); + } - SECTION("GetColumn with Invalid TypeId") { - TypeId invalid_type{999, TypeFlags::None, 0}; - REQUIRE_THROWS_AS(archetype.GetColumn(invalid_type), std::out_of_range); - } + SECTION("GetColumn with Invalid TypeId") + { + NodeType invalid_type{999, TypeFlags::None, 0}; + REQUIRE_THROWS_AS(archetype.GetColumn(invalid_type), std::out_of_range); + } } -TEST_CASE("Archetype State Validation") { - Types types_list = { GetTypeId().id }; - Archetype archetype(types_list); +TEST_CASE("Archetype State Validation", "[Archetype]") +{ + const Types types_list = {GetNodeType()}; + Archetype archetype(types_list); - SECTION("State After Allocation and Deallocation") { - size_t row1 = archetype.Allocate(); - size_t row2 = archetype.Allocate(); - REQUIRE(row1 != row2); + SECTION("State After Allocation and Deallocation") + { + size_t row1 = archetype.Allocate(); + size_t row2 = archetype.Allocate(); + REQUIRE(row1 != row2); - archetype.Deallocate(row1); - REQUIRE(archetype.rows[row1].next == 2); + archetype.Deallocate(row1); + REQUIRE(archetype.rows[row1].next == 2); - size_t row3 = archetype.Allocate(); - REQUIRE(row3 == row1); - } + size_t row3 = archetype.Allocate(); + REQUIRE(row3 == row1); + } } -TEST_CASE("Archetype Memory Consistency") { - Types types_list = { GetTypeId().id }; - Archetype archetype(types_list); +TEST_CASE("Archetype Memory Consistency", "[Archetype]") +{ + const Types types_list = {GetNodeType()}; + Archetype archetype(types_list); - SECTION("Memory Allocation and Deallocation Consistency") { - size_t row1 = archetype.Allocate(); - SH::span_dynamic& column = archetype.GetColumn(types_list[0]); - new(column[row1].as_ptr()) TestComponent{42}; + SECTION("Memory Allocation and De-allocation Consistency") + { + size_t row1 = archetype.Allocate(); + SH::span_dynamic &column = archetype.GetColumn(types_list[0]); + new (column[row1].as_ptr()) A{42, 10.3}; - archetype.Deallocate(row1); - size_t row2 = archetype.Allocate(); - REQUIRE(row1 == row2); + archetype.Deallocate(row1); + size_t row2 = archetype.Allocate(); + REQUIRE(row1 == row2); - new(column[row2].as_ptr()) TestComponent{84}; - REQUIRE(column[row2].as().value == 84); - } + new (column[row2].as_ptr()) A{84, 22.4f}; + REQUIRE(column[row2].as().a == 84); + REQUIRE(column[row2].as().b == 22.4f); + } } -struct ComplexComponent { - std::string value; - ComplexComponent(const std::string& val) : value(val) {} -}; - -TEST_CASE("Archetype Copy From for Complex Types") { - auto type_id = GetTypeId().id; - Types types_list_1 = { type_id }; +TEST_CASE("Archetype Copy From for Complex Types") +{ + auto type_id = GetNodeType(); + Types types_list_1 = {type_id}; Archetype archetype_1(types_list_1); - Types types_list_2 = { type_id }; + Types types_list_2 = {type_id}; Archetype archetype_2(types_list_2); archetype_1.Allocate(); - SH::span_dynamic& column = archetype_1.GetColumn(types_list_1[0]); - new(column[0].as_ptr()) ComplexComponent{"Hello, World!"}; + SH::span_dynamic &column = archetype_1.GetColumn(types_list_1[0]); + new (column[0].as_ptr()) B{"Hello, World!"}; - SECTION("CopyFrom another archetype with complex type") { - size_t target_row = archetype_2.Allocate(); - archetype_2.CopyFrom(archetype_1, 0, target_row); + SECTION("CopyFrom another archetype with complex type") + { + size_t target_row = archetype_2.Allocate(); + archetype_2.CopyFrom(archetype_1, 0, target_row); - SH::span_dynamic& target_column = archetype_2.GetColumn(types_list_2[0]); - REQUIRE(target_column[target_row].as().value == "Hello, World!"); + SH::span_dynamic &target_column = archetype_2.GetColumn(types_list_2[0]); + REQUIRE(target_column[target_row].as().value == "Hello, World!"); } } -TEST_CASE("Archetype Destructor and Cleanup") { - Types types_list = { GetTypeId(TypeFlags::None).id }; +TEST_CASE("Archetype Destructor and Cleanup") +{ + const Types types_list = {GetNodeType()}; - SECTION("Cleanup on Destruction") { - Archetype* archetype = new Archetype(types_list); - size_t row = archetype->Allocate(); - SH::span_dynamic& column = archetype->GetColumn(types_list[0]); - new(column[row].as_ptr()) int{42}; + SECTION("Cleanup on Destruction") + { + Archetype *archetype = new Archetype(types_list); + const size_t row = archetype->Allocate(); + SH::span_dynamic &column = archetype->GetColumn(types_list[0]); + new (column[row].as_ptr()) int{42}; - delete archetype; - // If there are memory leaks, the test runner will report them. - } + delete archetype; + // If there are memory leaks, the test runner will report them. In theory + } } - diff --git a/projs/experiments/ecs/tests/em_tests.cpp b/projs/experiments/ecs/tests/em_tests.cpp index c296c290..ed6bdab6 100644 --- a/projs/experiments/ecs/tests/em_tests.cpp +++ b/projs/experiments/ecs/tests/em_tests.cpp @@ -1,17 +1,33 @@ #include "./ecs.exp.h" #include "catch2/catch.hpp" -struct A {}; -struct Position {}; -struct C {}; +struct A : Component<> +{ + int a; + float b; + void Print() const override {}; +}; +struct B : Component<> +{ + void Print() const override {} +}; +struct C : Component<> +{ + void Print() const override {} +}; +struct Position : Component<> +{ + float x, y, z; + void Print() const override { printf("Pos: \n"); } +}; TEST_CASE("EM archetype storage") { SECTION("Gives the correct Archetype") { EntityManager em; auto& a = em.GetArchetype({ - GetTypeId().id, - GetTypeId(TypeFlags::Flag).id, - GetTypeId().id, + GetNodeType(), + GetNodeType(), + GetNodeType(), }); REQUIRE(a.id > 0); @@ -20,15 +36,15 @@ TEST_CASE("EM archetype storage") { SECTION("Gives the same Archetype") { EntityManager em; auto& a = em.GetArchetype({ - GetTypeId().id, - GetTypeId(TypeFlags::Flag).id, - GetTypeId().id, + GetNodeType(), + GetNodeType(), + GetNodeType(), }); auto& b = em.GetArchetype({ - GetTypeId().id, - GetTypeId(TypeFlags::Flag).id, - GetTypeId().id, + GetNodeType(), + GetNodeType(), + GetNodeType(), }); REQUIRE(a.id == b.id); @@ -39,9 +55,9 @@ TEST_CASE("EM archetype storage") { { EntityManager em; auto& a = em.GetArchetype({ - GetTypeId().id, - GetTypeId(TypeFlags::Flag).id, - GetTypeId().id, + GetNodeType(), + GetNodeType(), + GetNodeType(), }); for (auto [types, arch] : em.archetypes) diff --git a/projs/experiments/ecs/tests/id_tests.cpp b/projs/experiments/ecs/tests/id_tests.cpp index 88271111..072f0d49 100644 --- a/projs/experiments/ecs/tests/id_tests.cpp +++ b/projs/experiments/ecs/tests/id_tests.cpp @@ -8,16 +8,16 @@ struct C {}; TEST_CASE("id TESTS") { SECTION("Gives back ID") { const auto res = GetTypeId(); - REQUIRE(res.id.id > 0); + REQUIRE(res.id > 0); } SECTION("Gives back same ID") { const auto res1 = GetTypeId(); const auto res2 = GetTypeId(); - REQUIRE(res1.id.id == res2.id.id); + REQUIRE(res1.id == res2.id); } SECTION("Gives back different ID") { const auto res1 = GetTypeId(); const auto res2 = GetTypeId(); - REQUIRE(res1.id.id != res2.id.id); + REQUIRE(res1.id != res2.id); } } From 726fe6b9a3dcaf8cd77f3e11ecfbd736c83adf4d Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Mon, 23 Dec 2024 11:22:39 +0100 Subject: [PATCH 10/19] [WIP] --- projs/experiments/ecs/src/ecs.exp.h | 71 +++++++++++++++++++++++++++-- projs/experiments/ecs/src/main.cpp | 23 ++++++---- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/projs/experiments/ecs/src/ecs.exp.h b/projs/experiments/ecs/src/ecs.exp.h index 732fb624..9cd7dda9 100644 --- a/projs/experiments/ecs/src/ecs.exp.h +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -89,8 +89,12 @@ struct __attribute__((packed)) NodeType }; static_assert(sizeof(NodeType) == sizeof(uint64_t)); -inline int operator<(const NodeType& lhs, const NodeType& rhs){ return rhs.typeId > lhs.typeId; } +inline int operator<(const NodeType& lhs, const NodeType& rhs){ return rhs.typeId < lhs.typeId; } +inline int operator<=(const NodeType& lhs, const NodeType& rhs){ return rhs.typeId <= lhs.typeId; } +inline int operator>(const NodeType& lhs, const NodeType& rhs){ return rhs.typeId > lhs.typeId; } +inline int operator>=(const NodeType& lhs, const NodeType& rhs){ return rhs.typeId >= lhs.typeId; } inline bool operator==(const NodeType& lhs, const NodeType& rhs) { return lhs.typeId == rhs.typeId; } +inline bool operator!=(const NodeType& lhs, const NodeType& rhs) { return lhs.typeId != rhs.typeId; } template <> struct std::hash @@ -326,6 +330,9 @@ struct EntityRef template EntityRef &AddComponent(T &&val); + template + EntityRef &AddRelation(const EntityRef& other); + template EntityRef &RemoveComponent(); @@ -431,8 +438,6 @@ class EntityManager }; - - template EntityRef &EntityRef::AddComponent(T &&val) { @@ -440,6 +445,12 @@ EntityRef &EntityRef::AddComponent(T &&val) return *this; } template +EntityRef &EntityRef::AddRelation(const EntityRef& other) +{ + em->AddRelation(id, other.id); + return *this; +} +template EntityRef &EntityRef::RemoveComponent() { em->RemoveComponent(id); @@ -454,6 +465,60 @@ T& EntityRef::GetComponent() } +class ISystem +{ +public: + virtual ~ISystem() = default; + virtual void Run(EntityManager& em) = 0; +}; + +template +class System : public ISystem{ +public: + Types query; + std::function action; + + explicit System(const std::function &action) : action(action) { query = {GetNodeType()...}; } + + ~System() override; + + + void Run(EntityManager &em) override + { + for (auto archetype : em.archetypes) + { + if (std::ranges::includes(archetype.second.types, query)) + { + std::vector<> + } + } + } +}; + +class SystemManager +{ + std::vector systems; + + EntityManager& em; + +public: + explicit SystemManager(EntityManager &em) : em(em) {} + + void addSystem(ISystem& system) + { + systems.push_back(system); + } + + void runAllSystems() + { + for (ISystem &system : systems) + { + system.Run(em); + } + } +}; + + void PrintArchetype(const Archetype &a); void PrintEM(EntityManager &em); diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp index 49cfcf81..ecf6a790 100644 --- a/projs/experiments/ecs/src/main.cpp +++ b/projs/experiments/ecs/src/main.cpp @@ -29,20 +29,23 @@ struct Name final : Component<> }; int main() { - EntityManager em; + const auto a = em.AddEntity(); - auto a = em.AddEntity(); - - auto b = em.AddEntity(); - b.AddComponent({ 10, 22, 32}) - .AddComponent({}); - - b.GetComponent().x = 100; - - em.AddRelation(b.id,a.id); + for (int i = 0; i < 10; ++i) + { + auto b = em.AddEntity(); + b.AddComponent({ i, 22, 32}) + .AddComponent({}) + .AddRelation(a); + } PrintEM(em); + auto s = System([](Position& position) + { + position.z += 10; + }); + return 0; } From 50ed9dfb3cc636a8b5ec0d3ccbac610792f4c4a5 Mon Sep 17 00:00:00 2001 From: dpeter99 Date: Mon, 23 Dec 2024 11:22:54 +0100 Subject: [PATCH 11/19] [WIP] Idea settings --- .idea/editor.xml | 25 ++++++++++++++++++++ .idea/inspectionProfiles/Project_Default.xml | 3 ++- .idea/misc.xml | 3 +++ .idea/runConfigurations/test_game.xml | 2 +- .idea/runConfigurations/test_game_EDITOR.xml | 2 +- 5 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 .idea/editor.xml diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 00000000..5148668a --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index a98f236b..4924a98e 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,9 +1,10 @@ \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d965e880..bf4aebe5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + diff --git a/.idea/runConfigurations/test_game.xml b/.idea/runConfigurations/test_game.xml index 7c9a8be7..17aee76b 100644 --- a/.idea/runConfigurations/test_game.xml +++ b/.idea/runConfigurations/test_game.xml @@ -1,5 +1,5 @@ - + diff --git a/.idea/runConfigurations/test_game_EDITOR.xml b/.idea/runConfigurations/test_game_EDITOR.xml index a2a3fc81..b3d7db8a 100644 --- a/.idea/runConfigurations/test_game_EDITOR.xml +++ b/.idea/runConfigurations/test_game_EDITOR.xml @@ -1,5 +1,5 @@ - +