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/.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/.gitignore b/.gitignore index 22680f28..7c9c19fb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ cmake-build-debug-msvc/ cmake-build-debug-msvc-vs/ out/ .vs/ +.idea/ diff --git a/.gitmodules b/.gitmodules index 3fc45167..96862037 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,9 @@ -[submodule "projs/shadow/extern/spdlog"] - path = projs/shadow/extern/spdlog - url = https://github.com/gabime/spdlog.git [submodule "projs/shadow/extern/imgui"] path = projs/shadow/extern/imgui url = https://github.com/ocornut/imgui [submodule "projs/shadow/extern/dxmath"] path = projs/shadow/extern/dxmath url = https://github.com/microsoft/DirectXMath.git -[submodule "projs/shadow/extern/catch2"] - path = projs/shadow/extern/catch2 - url = https://github.com/catchorg/Catch2.git [submodule "projs/shadow/extern/glm"] path = projs/shadow/extern/glm url = https://github.com/g-truc/glm.git @@ -18,4 +12,13 @@ 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 = git@github.com:fmtlib/fmt.git +[submodule "projs/extern/spdlog"] + path = projs/extern/spdlog + url = https://github.com/gabime/spdlog.git +[submodule "projs/extern/catch2"] + path = projs/extern/catch2 + url = https://github.com/catchorg/Catch2.git diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 1c2fda56..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/cmake.xml b/.idea/cmake.xml deleted file mode 100644 index 89969254..00000000 --- a/.idea/cmake.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 4fe7f294..00000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 307554b7..00000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/customTargets.xml b/.idea/customTargets.xml deleted file mode 100644 index 93656d6a..00000000 --- a/.idea/customTargets.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 08cfc443..00000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/fileTemplates/Shadow Entitiy Header.h b/.idea/fileTemplates/Shadow Entitiy Header.h deleted file mode 100644 index 3273bb65..00000000 --- a/.idea/fileTemplates/Shadow Entitiy Header.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "Entity.h" -#include "TransformEntity.h" - -class ${NAME} : public ShadowEngine::Entities::TransformEntity{ - SHObject_Base(${NAME}); - - Entity_Base(${NAME}, TransformEntity); - -public: - void Build() override; -}; diff --git a/.idea/fileTemplates/Shadow Entitiy.cpp b/.idea/fileTemplates/Shadow Entitiy.cpp deleted file mode 100644 index a927cdc6..00000000 --- a/.idea/fileTemplates/Shadow Entitiy.cpp +++ /dev/null @@ -1 +0,0 @@ -void ${NAME}::Build() \ No newline at end of file diff --git a/.idea/fileTemplates/internal/C++ Class Header.h b/.idea/fileTemplates/internal/C++ Class Header.h deleted file mode 100644 index c84aecdc..00000000 --- a/.idea/fileTemplates/internal/C++ Class Header.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -${NAMESPACES_OPEN} - -class ${NAME} { - -}; - -${NAMESPACES_CLOSE} - diff --git a/.idea/fileTemplates/internal/C++ Class.cc b/.idea/fileTemplates/internal/C++ Class.cc deleted file mode 100644 index f40c5f55..00000000 --- a/.idea/fileTemplates/internal/C++ Class.cc +++ /dev/null @@ -1,4 +0,0 @@ -#[[#include]]# "${HEADER_FILENAME}" - -${NAMESPACES_OPEN_CPP} -${NAMESPACES_CLOSE_CPP} \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index a98f236b..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index d965e880..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 7e527079..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/test_game.xml b/.idea/runConfigurations/test_game.xml deleted file mode 100644 index 7c9a8be7..00000000 --- a/.idea/runConfigurations/test_game.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/test_game_EDITOR.xml b/.idea/runConfigurations/test_game_EDITOR.xml deleted file mode 100644 index a2a3fc81..00000000 --- a/.idea/runConfigurations/test_game_EDITOR.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/scopes/shadow.xml b/.idea/scopes/shadow.xml deleted file mode 100644 index 8e551766..00000000 --- a/.idea/scopes/shadow.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/statistic.xml b/.idea/statistic.xml deleted file mode 100644 index 06d20e2e..00000000 --- a/.idea/statistic.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/umbra.iml b/.idea/umbra.iml deleted file mode 100644 index 6d70257c..00000000 --- a/.idea/umbra.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 6b0b8b1b..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8070d769..726452aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,10 @@ include(shadow-modules.cmake) set(CMAKE_STATIC_LIBRARY_PREFIX "") set(CMAKE_SHARED_LIBRARY_PREFIX "") -add_subdirectory(projs/shadow) +add_subdirectory(projs/extern) -add_subdirectory(projs/test-game) \ No newline at end of file +#add_subdirectory(projs/shadow) + +#add_subdirectory(projs/test-game) + +add_subdirectory(projs/experiments/ecs) \ No newline at end of file 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..420d1e97 --- /dev/null +++ b/experiment-ecs-printer.py @@ -0,0 +1,32 @@ +import gdb.printing + + +class idPrinter: + """Print a TypeId object.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + return ("ID: " + str(self.val["id"]) + " Flags: " + str(self.val["flags"])) + +class TypeIdPairPrinter: + def __init__(self, val): + self.val = val + + def to_string(self): + 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', '^TypeId$', idPrinter) + pp.add_printer('id_pair', '^TypeId, int$', TypeIdPairPrinter) + + 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 new file mode 100644 index 00000000..29784173 --- /dev/null +++ b/projs/experiments/ecs/CMakeLists.txt @@ -0,0 +1,33 @@ +set(CMAKE_CXX_STANDARD 23) + +add_executable(experiment-ecs) +add_executable(experiment::ecs ALIAS experiment-ecs) + + +FILE(GLOB_RECURSE SOURCES + src/*.cpp +) + +target_sources(experiment-ecs PUBLIC ${SOURCES}) + +target_link_libraries(experiment-ecs + PRIVATE fmt::fmt-header-only +) + + +FILE(GLOB_RECURSE SOURCES_TESTS + src/id_system.cpp + src/span_dynamic.cpp + src/ecs.exp.cpp + tests/*.cpp +) + +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/_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..3b8604b7 --- /dev/null +++ b/projs/experiments/ecs/src/ecs.exp.cpp @@ -0,0 +1,188 @@ +#include "ecs.exp.h" + +#include +#include "lib/rang.hpp" + + +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::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; + types = sortTypes(types_list); + std::ranges::for_each(types, [&](const NodeType& type) + { + if(test(type.flags, TypeFlags::Flag)) + { + this->column_map.insert({type, -1}); + } + else + { + this->column_map.insert({type, next_c++}); + const TypeInfo& info = GetTypeInfoById(type.typeId); + void* page = malloc(info.size * PAGE_SIZE); + this->columns.push_back({page, info.size, PAGE_SIZE}); + } + }); + + 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, .in_use = false}); +} + + + + +Archetype &EntityManager::GetArchetype(const Types &types) +{ + if (archetypes.contains(types)) + { + return archetypes.at(types); + } + + Archetype a(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) +{ + std::cout << "# Archetype ID: " << a.id << std::endl; + std::cout << " Types:" << std::endl; + for (const auto& [type, column] : a.column_map) + { + std::cout << " \t" + << "Type: " << GetTypeNameByID(type.typeId) << " (" << type.typeId << ")" + <<" Column: " << column; + if(column >= 0) + { + std::cout << " Page: " << a.columns[column].begin().ptr(); + } + + std::cout << std::endl; + } + + for (int i = 0; i < a.rows.size(); ++i) + { + if(a.rows[i].in_use) + { + std::cout << rang::fg::green << "█" << rang::style::reset; + } + else + { + std::cout << rang::fg::gray << "█" << rang::style::reset; + } + if((i+1) % (PAGE_SIZE/4) == 0) + { + std::cout << std::endl; + } + } + std::cout << std::endl; + +} + +void PrintEM(EntityManager& em) +{ + std::cout << "Entity Manager" << std::endl; + + for (auto archetype : em.archetypes) + { + PrintArchetype(archetype.second); + } + + std::cout << "----------------" << std::endl; + std::cout << "\tEntities: " << std::endl; + + for (const auto& [id, record] : em.entities) + { + 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(); + } + 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 new file mode 100644 index 00000000..37a53e1d --- /dev/null +++ b/projs/experiments/ecs/src/ecs.exp.h @@ -0,0 +1,587 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "span_dynamic.h" + +#include "id_system.h" + +#define __OUT__ + +/** +* This file contains the Entity System +* The game world is built up from nodes +* Each Node has type +* - Entites +* - Components +* - Flags +* - Connections +*/ + + +/** + * Planned changes / known issues: + * - The Achetype class only supports a single page of data this will need to change + * For this we need an iterator abstraction ove the pages + * - The EntityManager should be able to create entities with multiple components at once + * - The addition of types to archetypes should be cached in a map for each archetype + * - The Archetypes of a system should be cached, and only recomputed for the new archetypes + * + * Features: + * - Add dependency between systems + * - The systems should be ordered by the types they require + * and allow for parallel execution of systems that don't depend on each other + * - add sparse components that are stored only by pointers to an outside storage + * this will allow for components that are too big to be stored in the archetype + * +*/ + +using EntityId = uint32_t; + + +/** +* Flag enum used to specify ECS node properties +* it is a 4 bit field the rest of the uint8 should not be used as it will be cut of +*/ +enum class TypeFlags : std::uint16_t { + None = 0, + Flag = 1 << 1, // Does not have a column in the archetype, has no data + Relation = 1 << 2, // The node is a relation to another node, the NodeType::entity field contains the id of the other node + Unused = 1 << 3, // Reserved for future use (Probably sparse components) + + SimpleRelation = Flag | Relation, // A simple relation to another node +}; + +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); +} + +/** + * Concept to check if a type has a static member called Flags of type TypeFlags + */ +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 NodeType +{ + TypeId typeId; + TypeFlags flags; + uint32_t entity; +} __attribute__((packed)); +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 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 +{ + 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; +} + + + + +/* + * example of the data structure + * 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 | + * + * + */ + +// #################################################### +// ################## Entity base classes ############# +// #################################################### + +template +class Component +{ +public: + static constexpr TypeFlags Flags = F; + + virtual ~Component() = default; + virtual void Print() const = 0; +}; + +class Entity : Component<> +{ + +}; + + +struct ChildOf final : Component +{ + void Print() const override + { + printf("ChildOf"); + } +}; + +// #################################################### +// ################### Archetype ###################### +// #################################################### + +// TODO: Find a better sorted storage for types +using Types = std::vector; + +Types sortTypes(Types t); + +/** + * Create a new type list with the type added + * @param t The base type list to add to + * @param id The type to add + * @return A new type list with the type added + */ +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; +} + +/** + * Create a new type list with the type removed + * @param t The base type list to remove from + * @param id The type to remove + * @return A new type list with the type removed + */ +inline Types removeType(const Types &t, const NodeType id) +{ + Types new_types = t; + std::erase(new_types, id); + new_types = sortTypes(new_types); //TODO: This is not necessary + return new_types; +} + +template <> +struct std::hash +{ + /** + * Hash function for the Types list + * @param vec The list of types + * @return The hash of the list + */ + std::size_t operator()(const Types &vec) const noexcept + { + std::size_t seed = vec.size(); + for (auto &i : vec) + { + seed ^= i.typeId + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } +}; + + + +struct RowMeta +{ + size_t next; + bool in_use; +}; + +constexpr size_t PAGE_SIZE = 2*2*2*2*2*2; + +/** + * Archetype is a collection of nodes with the same component types + * It stores the data in columns for each type + * + * TODO: Add support for multiple pages + */ +class Archetype +{ +public: + using Id = uint32_t; + static Id next_id; + + using ColumnMap = std::map; + + + Id id; + Types types; + ColumnMap column_map; + + + int next_free; + std::vector rows; + std::vector columns; + + + Archetype() = default; + + Archetype(const std::initializer_list types_list) : Archetype(std::vector(types_list)) {} + + explicit Archetype(const Types &types_list); + + SH::span_dynamic& GetColumn(NodeType id) + { + return columns[column_map.at(id)]; + } + + /** + * Returns the index of a new row or -1 if it is full + */ + size_t Allocate() + { + if(next_free == -1) + { + return -1; + } + auto row = next_free; + next_free = rows[row].next; + rows[row].next = -1; + rows[row].in_use = true; + return row; + } + + + void Deallocate(size_t row) + { + assert(row < rows.size() && "Invalid row index"); + 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 + { + assert(src_row < source.rows.size() && target_row < this->rows.size() && "Invalid row indices"); + + size_t source_index = 0; + size_t dest_index = 0; + + while (source_index < source.types.size() && dest_index < this->types.size()) + { + if (source.types[source_index] == this->types[dest_index]) + { + const int source_column_index = source.column_map.at(source.types[source_index]); + const int dest_column_index = this->column_map.at(this->types[dest_index]); + + auto& dest_column = this->columns[dest_column_index]; + auto& source_column = source.columns[source_column_index]; + + void *dest = dest_column[target_row].ptr(); + const void * src = source_column[src_row].ptr(); + + assert(dest_column.element_size() == source_column.element_size() && "Element sizes don't match"); + + std::memcpy(dest, src, source_column.element_size()); + + ++source_index; + ++dest_index; + } + else if (source.types[source_index] < this->types[dest_index]) + { + ++source_index; + } + else + { + ++dest_index; + } + } + } +}; + + +class EntityManager; + +struct EntityRef +{ + EntityManager *em; + EntityId id; + + EntityRef(EntityManager *em, const EntityId &id) : em(em), id(id) {} + + template + EntityRef &AddComponent(T &&val); + + template + EntityRef &AddRelation(const EntityRef& other); + + template + EntityRef &RemoveComponent(); + + template + T& GetComponent(); +}; + + +template +struct EntityRefTyped : EntityRef +{ + EntityRefTyped(EntityManager *em, const EntityId& id) : EntityRef(em, id) {} +}; + + +class EntityManager +{ +public: + std::unordered_map archetypes; + + + struct EntityRecord + { + Archetype* archetype; + size_t row; + }; + + std::unordered_map entities; + + EntityId next_id; + + EntityManager() : next_id(1){}; + + Archetype &GetArchetype(const Types &types); + + EntityId GetEntityId() + { + return next_id++; + } + + struct EntityCreationResult + { + EntityId id; + Archetype* archetype; + size_t row; + }; + + EntityCreationResult CreateEntity(const Types &type_id); + + void MoveEntity(const EntityId &id, const Types &types, __OUT__ EntityCreationResult &result); + + EntityRef AddEntity(); + + template + EntityRefTyped AddEntity() + { + NodeType type_id = GetTypeId().id; + auto [id, a, row] = CreateEntity({type_id}); + + T* ptr = a->GetColumn(type_id)[row].as_ptr(); + new(ptr) T(); + + return EntityRefTyped(this,id); + } + + template + void AddComponent(EntityId entity_id, T&& value) + { + const NodeType type_id = GetNodeType(); + auto& record = entities[entity_id]; + + Types new_types = addType(record.archetype->types, type_id); + + EntityCreationResult res; + MoveEntity(entity_id, new_types, res); + + T* ptr = res.archetype->GetColumn(type_id)[res.row].as_ptr(); + new(ptr)T(value); + } + + template + void RemoveComponent(EntityId entity_id) + { + const NodeType type_id = GetTypeId().id; + auto& record = entities[entity_id]; + + Types new_types = removeType(record.archetype->types, type_id); + + EntityCreationResult res; + MoveEntity(entity_id, new_types, res); + } + + 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); + + EntityCreationResult res; + MoveEntity(entity_id, new_types, res); + } + +}; + +template +EntityRef &EntityRef::AddComponent(T &&val) +{ + em->AddComponent(id, std::forward(val)); + return *this; +} +template +EntityRef &EntityRef::AddRelation(const EntityRef& other) +{ + em->AddRelation(id, other.id); + return *this; +} +template +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(); +} + + +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 { + // Iterate over all archetypes in the EntityManager. + for (auto &pair : em.archetypes) { + Archetype &arch = pair.second; + + // Check if the archetype contains all the requested types + bool matches = true; + for (const NodeType &req : query) { + auto it = arch.column_map.find(req); + // We require that the type is present and that it has a valid column (>= 0). + if (it == arch.column_map.end() || it->second < 0) { + matches = false; + break; + } + } + if (!matches) + continue; + + // For each row in the archetype that is in use, invoke the action. + for (size_t row = 0; row < arch.rows.size(); ++row) { + if (!arch.rows[row].in_use) + continue; + // Call the system's action with each component reference. + action( + *reinterpret_cast(arch.columns[arch.column_map.at(GetNodeType())][row].ptr())... + ); + } + } + } + +}; + +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/id_system.cpp b/projs/experiments/ecs/src/id_system.cpp new file mode 100644 index 00000000..7a1ee5a6 --- /dev/null +++ b/projs/experiments/ecs/src/id_system.cpp @@ -0,0 +1,21 @@ +#include "id_system.h" + +std::unordered_map typeMap; + +TypeId next_id = 1; + +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; +} diff --git a/projs/experiments/ecs/src/id_system.h b/projs/experiments/ecs/src/id_system.h new file mode 100644 index 00000000..ac68370a --- /dev/null +++ b/projs/experiments/ecs/src/id_system.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include + + +using TypeId = uint16_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 +{ + TypeId id; + std::string name; + size_t size; +}; +extern std::unordered_map typeMap; + +extern TypeId next_id; + + + +template +TypeInfo GetTypeId() +{ + const char* name = typeid(T).name(); + if(!typeMap.contains(name)) + { + TypeInfo newId = { + .id = next_id++, + .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); + + + diff --git a/projs/experiments/ecs/src/lib/rang.hpp b/projs/experiments/ecs/src/lib/rang.hpp new file mode 100644 index 00000000..831eda4c --- /dev/null +++ b/projs/experiments/ecs/src/lib/rang.hpp @@ -0,0 +1,502 @@ +#ifndef RANG_DOT_HPP +#define RANG_DOT_HPP + +#if defined(__unix__) || defined(__unix) || defined(__linux__) +#define OS_LINUX +#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) +#define OS_WIN +#elif defined(__APPLE__) || defined(__MACH__) +#define OS_MAC +#else +#error Unknown Platform +#endif + +#if defined(OS_LINUX) || defined(OS_MAC) +#include + +#elif defined(OS_WIN) + +#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) +#error \ + "Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA" +#elif !defined(_WIN32_WINNT) +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#endif + +#include +#include +#include + +// Only defined in windows 10 onwards, redefining in lower windows since it +// doesn't gets used in lower versions +// https://docs.microsoft.com/en-us/windows/console/getconsolemode +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif + +#endif + +#include +#include +#include +#include +#include + +namespace rang { + +/* For better compability with most of terminals do not use any style settings + * except of reset, bold and reversed. + * Note that on Windows terminals bold style is same as fgB color. + */ +enum class style { + reset = 0, + bold = 1, + dim = 2, + italic = 3, + underline = 4, + blink = 5, + rblink = 6, + reversed = 7, + conceal = 8, + crossed = 9 +}; + +enum class fg { + black = 30, + red = 31, + green = 32, + yellow = 33, + blue = 34, + magenta = 35, + cyan = 36, + gray = 37, + reset = 39 +}; + +enum class bg { + black = 40, + red = 41, + green = 42, + yellow = 43, + blue = 44, + magenta = 45, + cyan = 46, + gray = 47, + reset = 49 +}; + +enum class fgB { + black = 90, + red = 91, + green = 92, + yellow = 93, + blue = 94, + magenta = 95, + cyan = 96, + gray = 97 +}; + +enum class bgB { + black = 100, + red = 101, + green = 102, + yellow = 103, + blue = 104, + magenta = 105, + cyan = 106, + gray = 107 +}; + +enum class control { // Behaviour of rang function calls + Off = 0, // toggle off rang style/color calls + Auto = 1, // (Default) autodect terminal and colorize if needed + Force = 2 // force ansi color output to non terminal streams +}; +// Use rang::setControlMode to set rang control mode + +enum class winTerm { // Windows Terminal Mode + Auto = 0, // (Default) automatically detects wheter Ansi or Native API + Ansi = 1, // Force use Ansi API + Native = 2 // Force use Native API +}; +// Use rang::setWinTermMode to explicitly set terminal API for Windows +// Calling rang::setWinTermMode have no effect on other OS + +namespace rang_implementation { + + inline std::atomic &controlMode() noexcept + { + static std::atomic value(control::Auto); + return value; + } + + inline std::atomic &winTermMode() noexcept + { + static std::atomic termMode(winTerm::Auto); + return termMode; + } + + inline bool supportsColor() noexcept + { +#if defined(OS_LINUX) || defined(OS_MAC) + + static const bool result = [] { + const char *Terms[] + = { "ansi", "color", "console", "cygwin", "gnome", + "konsole", "kterm", "linux", "msys", "putty", + "rxvt", "screen", "vt100", "xterm" }; + + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) { + return false; + } + return std::any_of(std::begin(Terms), std::end(Terms), + [&](const char *term) { + return std::strstr(env_p, term) != nullptr; + }); + }(); + +#elif defined(OS_WIN) + // All windows versions support colors through native console methods + static constexpr bool result = true; +#endif + return result; + } + +#ifdef OS_WIN + + + inline bool isMsysPty(int fd) noexcept + { + // Dynamic load for binary compability with old Windows + const auto ptrGetFileInformationByHandleEx + = reinterpret_cast( + GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetFileInformationByHandleEx")); + if (!ptrGetFileInformationByHandleEx) { + return false; + } + + HANDLE h = reinterpret_cast(_get_osfhandle(fd)); + if (h == INVALID_HANDLE_VALUE) { + return false; + } + + // Check that it's a pipe: + if (GetFileType(h) != FILE_TYPE_PIPE) { + return false; + } + + // POD type is binary compatible with FILE_NAME_INFO from WinBase.h + // It have the same alignment and used to avoid UB in caller code + struct MY_FILE_NAME_INFO { + DWORD FileNameLength; + WCHAR FileName[MAX_PATH]; + }; + + auto pNameInfo = std::unique_ptr( + new (std::nothrow) MY_FILE_NAME_INFO()); + if (!pNameInfo) { + return false; + } + + // Check pipe name is template of + // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX + if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(), + sizeof(MY_FILE_NAME_INFO))) { + return false; + } + std::wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR)); + if ((name.find(L"msys-") == std::wstring::npos + && name.find(L"cygwin-") == std::wstring::npos) + || name.find(L"-pty") == std::wstring::npos) { + return false; + } + + return true; + } + +#endif + + inline bool isTerminal(const std::streambuf *osbuf) noexcept + { + using std::cerr; + using std::clog; + using std::cout; +#if defined(OS_LINUX) || defined(OS_MAC) + if (osbuf == cout.rdbuf()) { + static const bool cout_term = isatty(fileno(stdout)) != 0; + return cout_term; + } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { + static const bool cerr_term = isatty(fileno(stderr)) != 0; + return cerr_term; + } +#elif defined(OS_WIN) + if (osbuf == cout.rdbuf()) { + static const bool cout_term + = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout))); + return cout_term; + } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { + static const bool cerr_term + = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr))); + return cerr_term; + } +#endif + return false; + } + + template + using enableStd = typename std::enable_if< + std::is_same::value || std::is_same::value + || std::is_same::value || std::is_same::value + || std::is_same::value, + std::ostream &>::type; + + +#ifdef OS_WIN + + struct SGR { // Select Graphic Rendition parameters for Windows console + BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit + BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit + BYTE bold; // emulated as FOREGROUND_INTENSITY bit + BYTE underline; // emulated as BACKGROUND_INTENSITY bit + BOOLEAN inverse; // swap foreground/bold & background/underline + BOOLEAN conceal; // set foreground/bold to background/underline + }; + + enum class AttrColor : BYTE { // Color attributes for console screen buffer + black = 0, + red = 4, + green = 2, + yellow = 6, + blue = 1, + magenta = 5, + cyan = 3, + gray = 7 + }; + + inline HANDLE getConsoleHandle(const std::streambuf *osbuf) noexcept + { + if (osbuf == std::cout.rdbuf()) { + static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + return hStdout; + } else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) { + static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); + return hStderr; + } + return INVALID_HANDLE_VALUE; + } + + inline bool setWinTermAnsiColors(const std::streambuf *osbuf) noexcept + { + HANDLE h = getConsoleHandle(osbuf); + if (h == INVALID_HANDLE_VALUE) { + return false; + } + DWORD dwMode = 0; + if (!GetConsoleMode(h, &dwMode)) { + return false; + } + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (!SetConsoleMode(h, dwMode)) { + return false; + } + return true; + } + + inline bool supportsAnsi(const std::streambuf *osbuf) noexcept + { + using std::cerr; + using std::clog; + using std::cout; + if (osbuf == cout.rdbuf()) { + static const bool cout_ansi + = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf)); + return cout_ansi; + } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { + static const bool cerr_ansi + = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf)); + return cerr_ansi; + } + return false; + } + + inline const SGR &defaultState() noexcept + { + static const SGR defaultSgr = []() -> SGR { + CONSOLE_SCREEN_BUFFER_INFO info; + WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), + &info) + || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), + &info)) { + attrib = info.wAttributes; + } + SGR sgr = { 0, 0, 0, 0, FALSE, FALSE }; + sgr.fgColor = attrib & 0x0F; + sgr.bgColor = (attrib & 0xF0) >> 4; + return sgr; + }(); + return defaultSgr; + } + + inline BYTE ansi2attr(BYTE rgb) noexcept + { + static const AttrColor rev[8] + = { AttrColor::black, AttrColor::red, AttrColor::green, + AttrColor::yellow, AttrColor::blue, AttrColor::magenta, + AttrColor::cyan, AttrColor::gray }; + return static_cast(rev[rgb]); + } + + inline void setWinSGR(rang::bg col, SGR &state) noexcept + { + if (col != rang::bg::reset) { + state.bgColor = ansi2attr(static_cast(col) - 40); + } else { + state.bgColor = defaultState().bgColor; + } + } + + inline void setWinSGR(rang::fg col, SGR &state) noexcept + { + if (col != rang::fg::reset) { + state.fgColor = ansi2attr(static_cast(col) - 30); + } else { + state.fgColor = defaultState().fgColor; + } + } + + inline void setWinSGR(rang::bgB col, SGR &state) noexcept + { + state.bgColor = (BACKGROUND_INTENSITY >> 4) + | ansi2attr(static_cast(col) - 100); + } + + inline void setWinSGR(rang::fgB col, SGR &state) noexcept + { + state.fgColor + = FOREGROUND_INTENSITY | ansi2attr(static_cast(col) - 90); + } + + inline void setWinSGR(rang::style style, SGR &state) noexcept + { + switch (style) { + case rang::style::reset: state = defaultState(); break; + case rang::style::bold: state.bold = FOREGROUND_INTENSITY; break; + case rang::style::underline: + case rang::style::blink: + state.underline = BACKGROUND_INTENSITY; + break; + case rang::style::reversed: state.inverse = TRUE; break; + case rang::style::conceal: state.conceal = TRUE; break; + default: break; + } + } + + inline SGR ¤t_state() noexcept + { + static SGR state = defaultState(); + return state; + } + + inline WORD SGR2Attr(const SGR &state) noexcept + { + WORD attrib = 0; + if (state.conceal) { + if (state.inverse) { + attrib = (state.fgColor << 4) | state.fgColor; + if (state.bold) + attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } else { + attrib = (state.bgColor << 4) | state.bgColor; + if (state.underline) + attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + } else if (state.inverse) { + attrib = (state.fgColor << 4) | state.bgColor; + if (state.bold) attrib |= BACKGROUND_INTENSITY; + if (state.underline) attrib |= FOREGROUND_INTENSITY; + } else { + attrib = state.fgColor | (state.bgColor << 4) | state.bold + | state.underline; + } + return attrib; + } + + template + inline void setWinColorAnsi(std::ostream &os, T const value) + { + os << "\033[" << static_cast(value) << "m"; + } + + template + inline void setWinColorNative(std::ostream &os, T const value) + { + const HANDLE h = getConsoleHandle(os.rdbuf()); + if (h != INVALID_HANDLE_VALUE) { + setWinSGR(value, current_state()); + // Out all buffered text to console with previous settings: + os.flush(); + SetConsoleTextAttribute(h, SGR2Attr(current_state())); + } + } + + template + inline enableStd setColor(std::ostream &os, T const value) + { + if (winTermMode() == winTerm::Auto) { + if (supportsAnsi(os.rdbuf())) { + setWinColorAnsi(os, value); + } else { + setWinColorNative(os, value); + } + } else if (winTermMode() == winTerm::Ansi) { + setWinColorAnsi(os, value); + } else { + setWinColorNative(os, value); + } + return os; + } +#else + template + inline enableStd setColor(std::ostream &os, T const value) + { + return os << "\033[" << static_cast(value) << "m"; + } +#endif +} // namespace rang_implementation + +template +inline rang_implementation::enableStd operator<<(std::ostream &os, + const T value) +{ + const control option = rang_implementation::controlMode(); + switch (option) { + case control::Auto: + return rang_implementation::supportsColor() + && rang_implementation::isTerminal(os.rdbuf()) + ? rang_implementation::setColor(os, value) + : os; + case control::Force: return rang_implementation::setColor(os, value); + default: return os; + } +} + +inline void setWinTermMode(const rang::winTerm value) noexcept +{ + rang_implementation::winTermMode() = value; +} + +inline void setControlMode(const control value) noexcept +{ + rang_implementation::controlMode() = value; +} + +} // namespace rang + +#undef OS_LINUX +#undef OS_WIN +#undef OS_MAC + +#endif /* ifndef RANG_DOT_HPP */ diff --git a/projs/experiments/ecs/src/main.cpp b/projs/experiments/ecs/src/main.cpp new file mode 100644 index 00000000..c21af6cc --- /dev/null +++ b/projs/experiments/ecs/src/main.cpp @@ -0,0 +1,57 @@ +// +// Created by dpeter99 on 2023.09.10.. +// +#include "ecs.exp.h" + +struct A : Entity +{ + char sentinel[9] = "Entity A"; +}; + +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); + } +}; + +int main() { + EntityManager em; + SystemManager sm(em); + + const auto a = em.AddEntity(); + + for (int i = 0; i < 10; ++i) + { + auto b = em.AddEntity(); + b.AddComponent({ i, 22, 32}) + .AddComponent({}) + .AddRelation(a); + } + + auto s = System([](Position& position) + { + position.z += 10; + }); + + sm.addSystem(&s); + + sm.runAllSystems(); + + PrintEM(em); + + return 0; +} 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..5c394d09 --- /dev/null +++ b/projs/experiments/ecs/src/span_dynamic.h @@ -0,0 +1,187 @@ +#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()--; + } + + 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) + { + 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..422423a1 --- /dev/null +++ b/projs/experiments/ecs/tests/archetype.tests.cpp @@ -0,0 +1,188 @@ +#include "./ecs.exp.h" +#include "catch2/catch_all.hpp" + +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 De-allocation", "[Archetype]") +{ + // Create an archetype with a specific type + Types types_list = {GetNodeType()}; + 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 De-allocations") + { + 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); + // } +} + + +TEST_CASE("Archetype Column Manipulation", "[Archetype]") +{ + const Types types_list = {GetNodeType(), GetNodeType()}; + Archetype archetype(types_list); + + 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()) B{"asd"}; + + REQUIRE(column1[row].as().a == 42); + REQUIRE(column2[row].as().value == "asd"); + } + + 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", "[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); + + archetype.Deallocate(row1); + REQUIRE(archetype.rows[row1].next == 2); + + size_t row3 = archetype.Allocate(); + REQUIRE(row3 == row1); + } +} + +TEST_CASE("Archetype Memory Consistency", "[Archetype]") +{ + const Types types_list = {GetNodeType()}; + Archetype archetype(types_list); + + 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); + + new (column[row2].as_ptr()) A{84, 22.4f}; + REQUIRE(column[row2].as().a == 84); + REQUIRE(column[row2].as().b == 22.4f); + } +} + +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}; + 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()) 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); + + 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") +{ + const Types types_list = {GetNodeType()}; + + 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. In theory + } +} diff --git a/projs/experiments/ecs/tests/em_tests.cpp b/projs/experiments/ecs/tests/em_tests.cpp new file mode 100644 index 00000000..de5c04f0 --- /dev/null +++ b/projs/experiments/ecs/tests/em_tests.cpp @@ -0,0 +1,88 @@ +#include "./ecs.exp.h" +#include "catch2/catch_all.hpp" + +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({ + GetNodeType(), + GetNodeType(), + GetNodeType(), + }); + + REQUIRE(a.id > 0); + } + + SECTION("Gives the same Archetype") { + EntityManager em; + auto& a = em.GetArchetype({ + GetNodeType(), + GetNodeType(), + GetNodeType(), + }); + + auto& b = em.GetArchetype({ + GetNodeType(), + GetNodeType(), + GetNodeType(), + }); + + REQUIRE(a.id == b.id); + REQUIRE(&a == &b); + } + + SECTION("Archetype map matches internal Type list") + { + EntityManager em; + auto& a = em.GetArchetype({ + GetNodeType(), + GetNodeType(), + GetNodeType(), + }); + + 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); + +} + + + + diff --git a/projs/experiments/ecs/tests/id_tests.cpp b/projs/experiments/ecs/tests/id_tests.cpp new file mode 100644 index 00000000..94c4be5a --- /dev/null +++ b/projs/experiments/ecs/tests/id_tests.cpp @@ -0,0 +1,23 @@ +#include "./ecs.exp.h" +#include "catch2/catch_all.hpp" + +struct A {}; +struct Position {}; +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 new file mode 100644 index 00000000..3311f3fb --- /dev/null +++ b/projs/experiments/ecs/tests/span_dynamic.test.cpp @@ -0,0 +1,30 @@ +#include "span_dynamic.h" +#include "catch2/catch_all.hpp" + +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); + } +} \ 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..5cc840de --- /dev/null +++ b/projs/experiments/ecs/tests/tests.cpp @@ -0,0 +1,3 @@ +#define CATCH_CONFIG_MAIN +#include + diff --git a/projs/experiments/ecs2/CMakeLists.txt b/projs/experiments/ecs2/CMakeLists.txt new file mode 100644 index 00000000..90532f6f --- /dev/null +++ b/projs/experiments/ecs2/CMakeLists.txt @@ -0,0 +1,33 @@ +set(CMAKE_CXX_STANDARD 23) + +add_executable(experiment-ecs2) +add_executable(experiment::ecs2 ALIAS experiment-ecs2) + + +FILE(GLOB_RECURSE SOURCES + src/*.cpp +) + +target_sources(experiment-ecs2 PUBLIC ${SOURCES}) +target_include_directories(experiment-ecs2 PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(experiment-ecs2 + PRIVATE fmt::fmt-header-only +) + + +FILE(GLOB_RECURSE SOURCES_TESTS + src/id_system.cpp + src/span_dynamic.cpp + src/ecs.exp.cpp + tests/*.cpp +) + +add_executable(experiment-ecs2-tests) +target_link_libraries(experiment-ecs2-tests + PRIVATE Catch2::Catch2WithMain +) +target_compile_options(experiment-ecs2-tests + PUBLIC -fconcepts-diagnostics-depth=4 +) +target_sources(experiment-ecs2-tests PUBLIC ${SOURCES_TESTS}) +target_include_directories(experiment-ecs2-tests PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src) \ No newline at end of file diff --git a/projs/experiments/ecs2/Notes.txt b/projs/experiments/ecs2/Notes.txt new file mode 100644 index 00000000..829323f1 --- /dev/null +++ b/projs/experiments/ecs2/Notes.txt @@ -0,0 +1,49 @@ + // Columns are stored in Partitions. + // So why...? a single table has a single list of what type of columns are in it + // So we should store that somehwere? A list of column types... + // This is what my old NodeType was for.......... + // std::vector columns; + + + + * Fun fact: this is an ACTUAL database table, why link it to entities and components? :) + * Because I have ptsd from databses + * Look at me. Look. Me. Eyes. Brain. I'm the DB Teacher now. It's okay, you can release your trauma :P + * This is, coincidentally, my first time writing a DB from scratch, so I may take some time re-deriving these things, but we'll get there :P + * When all is said and done, wiring our ECS into this system should be REALLY easy. + * we could also write an ECS from the get go.. XD + * A high performance ECS is basically synonymous with a high capacity database / data storage system. It's all about where the data is placed and how it is managed. + * A database happens to be the most closely aligned concept to that definition, and also very simple to program in isolation. + * I know, ECS == In memory db with some funky querries + * The main advantage of writing this (and getting used to it, and using it) is that a database also solves some other problems in our code. + * Assets management by means of database tables is trivial. + * Assigning script elements by database association is not trivial but a lot easier than manually tracking things. + * Creating associations between database tables not via relations but by shared indices and inter-links solves things like Component childing (via Attachment Sockets and related) + * I was thinking of doing an MC style registry with entites... to use it as a DB sortoff + + + * I thik this is what I call pages... + * pages are an actual term in computing, be precise. + * 1 page = 4096 bytes. + * 1 partition = 1 file system on the HDD XD + * "partitioned data storage" is also an actual term in computing, and is what most databases in the real world are built on. + * Did i ever mention my DB teacher was stupid?? + * Look at me. I'm the DB teacher now. + * :glare: + * :bolbshrug: + + + /* + is this how you want them to be? + so the table class itself is stored inside the allocation? + + v table 1 v table 2 + [ (Table data [ ID, Pos, Light, Mesh, ID, Pos, Light, Mesh ]) (Table data [ ID, Pos, RigBody, Collider, ID, Pos, RigBody, Collider ]) ] + ^ row 1 ^ row 2 ^ row 1 ^ row 2 + */ + + Well sure will look at the intel docs for those magick lines later + If you say there is a CPU instruction for this I guess :shrug: they keep adding new one + This one is from 1998, and was implemented prior to the existence of 64 bit processors. + As i said, I'm not up to date with the new stuff, this wasn't in ANSI C that i learned + yike \ No newline at end of file diff --git a/projs/experiments/ecs2/include/ecs.h b/projs/experiments/ecs2/include/ecs.h new file mode 100644 index 00000000..90e41651 --- /dev/null +++ b/projs/experiments/ecs2/include/ecs.h @@ -0,0 +1,268 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* +Target memory layout: +v table 1 v table 2 +[ [ ID, Pos, Light, Mesh, ID, Pos, Light, Mesh ] [ ID, Pos, RigBody, Collider, ID, Pos, RigBody, Collider ] + ^ row 1 ^ row 2 ^ row 1 ^ row 2 +*/ + + +/** + * A partition is an allocated buffer with a fixed maximum size, accessible in a linked list + * It is the data storage used by a table, stored in a static array. + * All partitions should end up allocated (i can't remember the proper word for here: one after another) but this allows for efficient access when the system malloc function simply won't let you + */ +class Partition { + static constexpr uint32_t CAPACITY = 256; +public: + + // Let's just use a linked list of free slots as the vector means extra allocations + //C: The vector will not be cached, and will only be examined when a new row is being added to the table (which should be a very rare occasion) + // Meanwhile, we'd have to navigate the full linked list of free slots every time a read occurs to make sure that we aren't skipping over empty (freshly allocated but still valid) rows. + // Allocation function is the best option here. + /* + We could strore the data like this: + [ + (Entiy Id, [Position, ...]) + (-1 , nextFree) + ] + so we have both an easy jump list for adding new rows and a sentinel value to check if the row is used when iterating in the system? + Having a single item describing the next single free slot is limiting, and has some potential problems: + - forgetting to update the flag when a slot opens before the current freeSlot means that the new value is completely forgotten about, and leaked. + - allocating a large number of items in one go means updating the single value in a tight loop, making it basically useless; just scan the list once and track all. + - nextFree introduces a point of contention: what do we do when the Partition is full? there's no free value. Would it be null? how would we tell the rest of the code that uses partition how to deal with null values? + - A list of free indices solves all of these problems; updating happens in one location. Updating the list with a new set of values in one batch is easily done. When the list is empty, there are no slots, and you don't need specific nullptr handling. + */ + //void *nextFree; + + + + // Allocating a new row is the same as recycling a freed existing row, and are very different operations, so we wrap them in one: + /** + * Provides an index of a valid entry into the Partition. + * @returns the index of a usable row, throws if the partition is full. + * does a short look like a pointer? that's the index of the row. + +Just realized... Pagination... that is why i called them pages... + */ + uint16_t AllocateRow(); + + // We keep the Partition constructor as no-arguments, default implementation to take advantage of some compiler inlining optimizations later on. + // "It's a secret tool we'll use later." + Partition() = default; + + explicit Partition(uint16_t id): partitionID(id) {} + ~Partition(); + + // A table may use multiple partitions, so we may need a way to uniquely identify them without traversing the full list every time. + // The table creating this Partition is in charge of assigning IDs. + uint16_t partitionID = 0; + + // Link these partitions to adjacent elements, forming a linked list + Partition* next = nullptr; + // May remain nullptr if is the first element in the list + Partition* prev = nullptr; + + // All partitions are buffers of the same number of rows, so we need not note how many elements are present; we can assume that there are always null/default elements present. + + uint32_t numUsedRows = 0; + // We can remove rows at will, and reordering the partition at runtime may cause problems, so we offer a list of indices of elements that can be reused in lieu of moving memory around constantly. + // This allows + std::vector freeRows; + + // Columns of a table are typed attributes describing Components. They are important for reading the table data, and we hold a copy of that data here. + std::vector columns; + + // Release the row data to the Partition to be reused, leaving an empty place in the list. + void FreeRow(uint16_t idx); + // Immediately purge the row data by swapping the contents with the last valid row, reducing the number of used rows. + void EraseRow(uint16_t idx); +}; + +/// describe it please +/** +* Describes a single cpp type +* Currently used for specifying the size of a Component +* cause we STILL don't have reflection in cpp (but maybe in cpp36) + // Columns of a table are typed attributes describing Components. +* this would be the same as my old class +* this is only for the interface you can map them to columns or something else internally + +*/ +struct Type +{ + public: + using ID = uint16_t; + + ID id; + std::string name; + size_t size; + + bool needsConstructor = false; + void* defaultValue = nullptr; + + // A secret tool that we'll use later. + uint32_t secret = 0; + + // We need to be able to default-allocate so that we can store Attributes in a vector + Type() = default; + // We also need to be able to move. + Type(Type&& attr) noexcept; + ~Type(); + + void operator=(Type&& attr) noexcept; + void operator=(Type& attr); +}; + +/** + * I am not sure if this will work, but I think this solves 3 problems with your current solution by itself. + * A mask, identifying tables with a certain set of components. + * Allows retrieving all instances of a set of Components very, very quickly. + * Cannot uniquely identify all tables, as such. + * what... A mask... as in a bit filed mask? + * Yes, a bitmask. + * So you would have as many bits s there are componentTypes ? + * 16 bits will represent a component type. 128 bits per register = 8 columns per registry that we can mask off. + * in the VAST MAJORITY of cases, a single table will fit in a single register, and we can just load table signatures into a register and mask them repeatedly to get the list of tables with a set of components. + * It's an extremely fast way to fetch all [Position,Mesh] component pairs from all tables without iterating the contents OR the actual Attribute/type data. + * + +|----- CPU register ----| +|-16-|-16-|-16-|-16-|-16-|-16-|-16-|-16-| + Ligh Pos Mesh +searhing [Pos, Mesh] + +you still need to match each offset insied the register.. +we need to check all places for Pos and than all places after the Pos for the Mesh +Or I'm missing what you mean + +The best part about SIMD is that it doesn't care about what OR normally does. + + for (int i = 0; i < size; i++) + { + __m128i temp = _mm_and_si128(src.mask[i], mask.mask[i]); + __m128i cmp = _mm_cmpeq_epi32(temp, _mm_setzero_si128()); + if ((_mm_movemask_epi8(cmp) == 0xFFFF)) + return false; + }; + + Behold. src.mask is a TableSignature containing [ Pos, Mesh, 0, 0, 0, ...] and mask.mask is our data. + As long as Pos and Mesh are there SOMEWHERE, SIMD's and followed by cmpeq and then movemask will catch any combination. + * + */ +class Signature { + public: + Signature() : data(nullptr), len(0) {}; + Signature(std::initializer_list const& attrs); + Signature(uint16_t* attrs, size_t len); + ~Signature(); + + const bool operator==(const Signature& other) const; + + const bool IsValid() const { return len > 0; } + + const bool IsPresent(uint16_t attr) const; + + void Flip(uint16_t attr); + void Set(uint16_t attr); + void Clear(uint16_t attr); + + static const bool CheckAll(Signature const& sig, Signature const& other); + static const bool CheckAny(Signature const& sig, Signature const& other); + +protected: + // An up-to-1024-bit buffer backed by SSE registers. + __m128i* data; + uint8_t len; +}; + +using EntityID = uint64_t; +struct RowMeta{ + EntityID id; +}; + +/** + * Stores all entites (rows) that have the same types of components (values?) +*/ +class Table { + + // All Attributes that this table contains. Not a unique identifier. + Signature signature; + + // The actual types of the columns + std::vector types; + + // A unique ID for the table, but only at runtime. + uint32_t id; + + // The total number of rows of data across all Partitions in the table. + uint32_t rows; + + + // All partitions in the table. + std::vector partitions; + + // Partition access accelaration structures: + // The current partition, containing the entry that will next be added to the list. + // the next partition with a free slot + Partition* activePartition = nullptr; + // The first partition with usable data. + // what do you mean usable data? is this the start of the linked list? + // why are we doing a linked list if we have a vector of all the partitions? + // Usable means that it is not default initialized. + // so that it is not empty? + // A Partition can hold 256 entities. + // If we create 800 (3.3 partitions) entities and then do stuff, then load 200 more and unload the first 800 after, we have 2 full Partitions of default-initialized data. + // This points to that sixth partition, which may not be the first ACTUAL partition. This effectively allows us to iterate all "actual" entities by following the linked list. + // AKA, yes, it skips the empty partitions. + // Sleep is for the weak + Partition* firstPartition = nullptr; + + // If we delete a large number of rows at once, we may end up having partitions empty prior to the active partition. This can keep track of the ones we need to re-use. + std::vector emptyPartitions; + + /** + * Later this will need a global allocator to use for the table + * @param types List of component types in this Table + */ + Table( + std::vector types + ){ + this->signature = Signature(/*types.map(t->t.id)*/); + + /*Calculate a single entity's size*/ + size_t size = 0; + size += sizeof(RowMeta); + for(auto & type : types){ + size += type.size; + } + + activePartition = new Partition(); + partitions.push_back(activePartition); + } + + // Return type is up for discussion + void* Allocate(); + void Free(void* ptr); + // Moves an already existing row from an other table + void Adopt(void* ptr); + +}; \ No newline at end of file diff --git a/projs/extern/CMakeLists.txt b/projs/extern/CMakeLists.txt new file mode 100644 index 00000000..af50184c --- /dev/null +++ b/projs/extern/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(catch2) +add_subdirectory(fmt) +add_subdirectory(spdlog) \ No newline at end of file diff --git a/projs/extern/catch2 b/projs/extern/catch2 new file mode 160000 index 00000000..914aeecf --- /dev/null +++ b/projs/extern/catch2 @@ -0,0 +1 @@ +Subproject commit 914aeecfe23b1e16af6ea675a4fb5dbd5a5b8d0a 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/extern/spdlog b/projs/extern/spdlog new file mode 160000 index 00000000..3335c380 --- /dev/null +++ b/projs/extern/spdlog @@ -0,0 +1 @@ +Subproject commit 3335c380a08c5e0f5117a66622df6afdb3d74959 diff --git a/projs/shadow/CMakeLists.txt b/projs/shadow/CMakeLists.txt index 32c43d0a..f3a373a3 100644 --- a/projs/shadow/CMakeLists.txt +++ b/projs/shadow/CMakeLists.txt @@ -4,13 +4,10 @@ project(shadow) add_subdirectory(extern/dxmath) add_subdirectory(extern/glm) SET(SPDLOG_BUILD_PIC true) -add_subdirectory(extern/spdlog) add_subdirectory(extern/dylib) add_subdirectory(extern/vulkan_memory_allocator) -add_subdirectory(extern/catch2) include(extern/SDL2/CMakeLists.txt) - # Core engine add_subdirectory(shadow-engine) diff --git a/projs/shadow/extern/catch2 b/projs/shadow/extern/catch2 deleted file mode 160000 index 9c541ca7..00000000 --- a/projs/shadow/extern/catch2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9c541ca72e7857dec71d8a41b97e42c2f1c92602 diff --git a/projs/shadow/extern/spdlog b/projs/shadow/extern/spdlog deleted file mode 160000 index cedfeeb9..00000000 --- a/projs/shadow/extern/spdlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cedfeeb95f3af11df7d3b1e7e0d3b86b334dc23b 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/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 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; };